50
|
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 <[email protected]> |
|
6 |
;; Lennart Staflin <[email protected]> |
|
7 |
;; Phil Hagelberg <[email protected]> |
|
8 |
;; URL: http://github.com/technomancy/clojure-mode |
|
9 |
;; Version: 1.11.5 |
|
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" 'clojure-display-inferior-lisp-buffer) |
|
111 |
(define-key map (kbd "RET") 'reindent-then-newline-and-indent) |
|
112 |
(define-key map (kbd "C-c t") 'clojure-jump-to-test) |
|
113 |
(define-key map (kbd "C-c M-q") 'clojure-fill-docstring) |
|
114 |
map) |
|
115 |
"Keymap for Clojure mode. Inherits from `lisp-mode-shared-map'.") |
|
116 |
|
|
117 |
(defvar clojure-mode-syntax-table |
|
118 |
(let ((table (copy-syntax-table emacs-lisp-mode-syntax-table))) |
|
119 |
(modify-syntax-entry ?~ "' " table) |
|
120 |
;; can't safely make commas whitespace since it will apply even |
|
121 |
;; inside string literals--ick! |
|
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 |
(modify-syntax-entry ?^ "'" table) |
|
128 |
table)) |
|
129 |
|
|
130 |
(defvar clojure-mode-abbrev-table nil |
|
131 |
"Abbrev table used in clojure-mode buffers.") |
|
132 |
|
|
133 |
(define-abbrev-table 'clojure-mode-abbrev-table ()) |
|
134 |
|
|
135 |
(defvar clojure-prev-l/c-dir/file nil |
|
136 |
"Record last directory and file used in loading or compiling. |
|
137 |
This holds a cons cell of the form `(DIRECTORY . FILE)' |
|
138 |
describing the last `clojure-load-file' or `clojure-compile-file' command.") |
|
139 |
|
|
140 |
(defvar clojure-test-ns-segment-position -1 |
|
141 |
"Which segment of the ns is \"test\" inserted in your test name convention. |
|
142 |
|
|
143 |
Customize this depending on your project's conventions. Negative |
|
144 |
numbers count from the end: |
|
145 |
|
|
146 |
leiningen.compile -> leiningen.test.compile (uses 1) |
|
147 |
clojure.http.client -> clojure.http.test.client (uses -1)") |
|
148 |
|
|
149 |
(defun clojure-mode-version () |
|
150 |
"Currently package.el doesn't support prerelease version numbers." |
|
151 |
"1.11.5") |
|
152 |
|
|
153 |
;;;###autoload |
|
154 |
(defun clojure-mode () |
|
155 |
"Major mode for editing Clojure code - similar to Lisp mode. |
|
156 |
Commands: |
|
157 |
Delete converts tabs to spaces as it moves back. |
|
158 |
Blank lines separate paragraphs. Semicolons start comments. |
|
159 |
\\{clojure-mode-map} |
|
160 |
Note that `run-lisp' may be used either to start an inferior Lisp job |
|
161 |
or to switch back to an existing one. |
|
162 |
|
|
163 |
Entry to this mode calls the value of `clojure-mode-hook' |
|
164 |
if that value is non-nil." |
|
165 |
(interactive) |
|
166 |
(kill-all-local-variables) |
|
167 |
(use-local-map clojure-mode-map) |
|
168 |
(setq mode-name "Clojure" |
|
169 |
major-mode 'clojure-mode |
|
170 |
imenu-create-index-function |
|
171 |
(lambda () |
|
172 |
(imenu--generic-function '((nil clojure-match-next-def 0)))) |
|
173 |
local-abbrev-table clojure-mode-abbrev-table |
|
174 |
indent-tabs-mode nil) |
|
175 |
(lisp-mode-variables nil) |
|
176 |
(set-syntax-table clojure-mode-syntax-table) |
|
177 |
(set (make-local-variable 'comment-start-skip) |
|
178 |
"\\(\\(^\\|[^\\\\\n]\\)\\(\\\\\\\\\\)*\\)\\(;+\\|#|\\) *") |
|
179 |
(set (make-local-variable 'lisp-indent-function) |
|
180 |
'clojure-indent-function) |
|
181 |
(when (< emacs-major-version 24) |
|
182 |
(set (make-local-variable 'forward-sexp-function) |
|
183 |
'clojure-forward-sexp)) |
|
184 |
(set (make-local-variable 'lisp-doc-string-elt-property) |
|
185 |
'clojure-doc-string-elt) |
|
186 |
(set (make-local-variable 'inferior-lisp-program) "lein repl") |
|
187 |
(set (make-local-variable 'parse-sexp-ignore-comments) t) |
|
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-display-inferior-lisp-buffer () |
|
200 |
"Display a buffer bound to `inferior-lisp-buffer'." |
|
201 |
(interactive) |
|
202 |
(if (and inferior-lisp-buffer (get-buffer inferior-lisp-buffer)) |
|
203 |
(pop-to-buffer inferior-lisp-buffer t) |
|
204 |
(run-lisp inferior-lisp-program))) |
|
205 |
|
|
206 |
(defun clojure-load-file (file-name) |
|
207 |
"Load a Lisp file into the inferior Lisp process." |
|
208 |
(interactive (comint-get-source "Load Clojure file: " |
|
209 |
clojure-prev-l/c-dir/file |
|
210 |
'(clojure-mode) t)) |
|
211 |
(comint-check-source file-name) ; Check to see if buffer needs saved. |
|
212 |
(setq clojure-prev-l/c-dir/file (cons (file-name-directory file-name) |
|
213 |
(file-name-nondirectory file-name))) |
|
214 |
(comint-send-string (inferior-lisp-proc) |
|
215 |
(format clojure-mode-load-command file-name)) |
|
216 |
(switch-to-lisp t)) |
|
217 |
|
|
218 |
|
|
219 |
|
|
220 |
(defun clojure-match-next-def () |
|
221 |
"Scans the buffer backwards for the next top-level definition. |
|
222 |
Called by `imenu--generic-function'." |
|
223 |
(when (re-search-backward "^\\s *(def\\S *[ \n\t]+" nil t) |
|
224 |
(save-excursion |
|
225 |
(goto-char (match-end 0)) |
|
226 |
(when (looking-at "#?\\^") |
|
227 |
(let (forward-sexp-function) ; using the built-in one |
|
228 |
(forward-sexp))) ; skip the metadata |
|
229 |
(re-search-forward "[^ \n\t)]+")))) |
|
230 |
|
|
231 |
(defun clojure-mode-font-lock-setup () |
|
232 |
"Configures font-lock for editing Clojure code." |
|
233 |
(interactive) |
|
234 |
(set (make-local-variable 'font-lock-multiline) t) |
|
235 |
(add-to-list 'font-lock-extend-region-functions |
|
236 |
'clojure-font-lock-extend-region-def t) |
|
237 |
|
|
238 |
(when clojure-mode-font-lock-comment-sexp |
|
239 |
(add-to-list 'font-lock-extend-region-functions |
|
240 |
'clojure-font-lock-extend-region-comment t) |
|
241 |
(make-local-variable 'clojure-font-lock-keywords) |
|
242 |
(add-to-list 'clojure-font-lock-keywords |
|
243 |
'clojure-font-lock-mark-comment t) |
|
244 |
(set (make-local-variable 'open-paren-in-column-0-is-defun-start) nil)) |
|
245 |
|
|
246 |
(setq font-lock-defaults |
|
247 |
'(clojure-font-lock-keywords ; keywords |
|
248 |
nil nil |
|
249 |
(("+-*/.<>=!?$%_&~^:@" . "w")) ; syntax alist |
|
250 |
nil |
|
251 |
(font-lock-mark-block-function . mark-defun) |
|
252 |
(font-lock-syntactic-face-function |
|
253 |
. lisp-font-lock-syntactic-face-function)))) |
|
254 |
|
|
255 |
(defun clojure-font-lock-def-at-point (point) |
|
256 |
"Find the position range between the top-most def* and the |
|
257 |
fourth element afterwards. Note that this means there's no |
|
258 |
gaurantee of proper font locking in def* forms that are not at |
|
259 |
top-level." |
|
260 |
(goto-char point) |
|
261 |
(condition-case nil |
|
262 |
(beginning-of-defun) |
|
263 |
(error nil)) |
|
264 |
|
|
265 |
(let ((beg-def (point))) |
|
266 |
(when (and (not (= point beg-def)) |
|
267 |
(looking-at "(def")) |
|
268 |
(condition-case nil |
|
269 |
(progn |
|
270 |
;; move forward as much as possible until failure (or success) |
|
271 |
(forward-char) |
|
272 |
(dotimes (i 4) |
|
273 |
(forward-sexp))) |
|
274 |
(error nil)) |
|
275 |
(cons beg-def (point))))) |
|
276 |
|
|
277 |
(defun clojure-font-lock-extend-region-def () |
|
278 |
"Move fontification boundaries to always include the first four |
|
279 |
elements of a def* forms." |
|
280 |
(let ((changed nil)) |
|
281 |
(let ((def (clojure-font-lock-def-at-point font-lock-beg))) |
|
282 |
(when def |
|
283 |
(destructuring-bind (def-beg . def-end) def |
|
284 |
(when (and (< def-beg font-lock-beg) |
|
285 |
(< font-lock-beg def-end)) |
|
286 |
(setq font-lock-beg def-beg |
|
287 |
changed t))))) |
|
288 |
|
|
289 |
(let ((def (clojure-font-lock-def-at-point font-lock-end))) |
|
290 |
(when def |
|
291 |
(destructuring-bind (def-beg . def-end) def |
|
292 |
(when (and (< def-beg font-lock-end) |
|
293 |
(< font-lock-end def-end)) |
|
294 |
(setq font-lock-end def-end |
|
295 |
changed t))))) |
|
296 |
changed)) |
|
297 |
|
|
298 |
(defun clojure-font-lock-extend-region-comment () |
|
299 |
"Move fontification boundaries to always contain |
|
300 |
entire (comment ..) sexp. Does not work if you have a |
|
301 |
white-space between ( and comment, but that is omitted to make |
|
302 |
this run faster." |
|
303 |
(let ((changed nil)) |
|
304 |
(goto-char font-lock-beg) |
|
305 |
(condition-case nil (beginning-of-defun) (error nil)) |
|
306 |
(let ((pos (re-search-forward "(comment\\>" font-lock-end t))) |
|
307 |
(when pos |
|
308 |
(forward-char -8) |
|
309 |
(when (< (point) font-lock-beg) |
|
310 |
(setq font-lock-beg (point) |
|
311 |
changed t)) |
|
312 |
(condition-case nil (forward-sexp) (error nil)) |
|
313 |
(when (> (point) font-lock-end) |
|
314 |
(setq font-lock-end (point) |
|
315 |
changed t)))) |
|
316 |
changed)) |
|
317 |
|
|
318 |
(defun clojure-font-lock-mark-comment (limit) |
|
319 |
"Marks all (comment ..) forms with font-lock-comment-face." |
|
320 |
(let (pos) |
|
321 |
(while (and (< (point) limit) |
|
322 |
(setq pos (re-search-forward "(comment\\>" limit t))) |
|
323 |
(when pos |
|
324 |
(forward-char -8) |
|
325 |
(condition-case nil |
|
326 |
(add-text-properties (1+ (point)) (progn |
|
327 |
(forward-sexp) (1- (point))) |
|
328 |
'(face font-lock-comment-face multiline t)) |
|
329 |
(error (forward-char 8)))))) |
|
330 |
nil) |
|
331 |
|
|
332 |
(defconst clojure-font-lock-keywords |
|
333 |
(eval-when-compile |
|
334 |
`( ;; Definitions. |
|
335 |
(,(concat "(\\(?:clojure.core/\\)?\\(" |
|
336 |
(regexp-opt '("defn" "defn-" "def" "def-" "defonce" |
|
337 |
"defmulti" "defmethod" "defmacro" |
|
338 |
"defstruct" "deftype" "defprotocol" |
|
339 |
"defrecord" "deftest" |
|
340 |
"slice" "def\\[a-z\\]" |
|
341 |
"defalias" "defhinted" "defmacro-" |
|
342 |
"defn-memo" "defnk" "defonce-" |
|
343 |
"defstruct-" "defunbound" "defunbound-" |
|
344 |
"defvar" "defvar-")) |
|
345 |
;; Function declarations. |
|
346 |
"\\)\\>" |
|
347 |
;; Any whitespace |
|
348 |
"[ \r\n\t]*" |
|
349 |
;; Possibly type or metadata |
|
350 |
"\\(?:#?^\\(?:{[^}]*}\\|\\sw+\\)[ \r\n\t]*\\)*" |
|
351 |
"\\(\\sw+\\)?") |
|
352 |
(1 font-lock-keyword-face) |
|
353 |
(2 font-lock-function-name-face nil t)) |
|
354 |
;; Deprecated functions |
|
355 |
(,(concat |
|
356 |
"(\\(?:clojure.core/\\)?" |
|
357 |
(regexp-opt |
|
358 |
'("add-watcher" "remove-watcher" "add-classpath") t) |
|
359 |
"\\>") |
|
360 |
1 font-lock-warning-face) |
|
361 |
;; Control structures |
|
362 |
(,(concat |
|
363 |
"(\\(?:clojure.core/\\)?" |
|
364 |
(regexp-opt |
|
365 |
'("let" "letfn" "do" |
|
366 |
"case" "cond" "condp" |
|
367 |
"for" "loop" "recur" |
|
368 |
"when" "when-not" "when-let" "when-first" |
|
369 |
"if" "if-let" "if-not" |
|
370 |
"." ".." "->" "->>" "doto" |
|
371 |
"and" "or" |
|
372 |
"dosync" "doseq" "dotimes" "dorun" "doall" |
|
373 |
"load" "import" "unimport" "ns" "in-ns" "refer" |
|
374 |
"try" "catch" "finally" "throw" |
|
375 |
"with-open" "with-local-vars" "binding" |
|
376 |
"gen-class" "gen-and-load-class" "gen-and-save-class" |
|
377 |
"handler-case" "handle") t) |
|
378 |
"\\>") |
|
379 |
1 font-lock-builtin-face) |
|
380 |
;; Built-ins |
|
381 |
(,(concat |
|
382 |
"(\\(?:clojure.core/\\)?" |
|
383 |
(regexp-opt |
|
384 |
'("*" "*1" "*2" "*3" "*agent*" |
|
385 |
"*allow-unresolved-vars*" "*assert*" "*clojure-version*" "*command-line-args*" "*compile-files*" |
|
386 |
"*compile-path*" "*e" "*err*" "*file*" "*flush-on-newline*" |
|
387 |
"*in*" "*macro-meta*" "*math-context*" "*ns*" "*out*" |
|
388 |
"*print-dup*" "*print-length*" "*print-level*" "*print-meta*" "*print-readably*" |
|
389 |
"*read-eval*" "*source-path*" "*use-context-classloader*" "*warn-on-reflection*" "+" |
|
390 |
"-" "/" |
|
391 |
"<" "<=" "=" "==" ">" |
|
392 |
">=" "accessor" "aclone" |
|
393 |
"agent" "agent-errors" "aget" "alength" "alias" |
|
394 |
"all-ns" "alter" "alter-meta!" "alter-var-root" "amap" |
|
395 |
"ancestors" "and" "apply" "areduce" "array-map" |
|
396 |
"aset" "aset-boolean" "aset-byte" "aset-char" "aset-double" |
|
397 |
"aset-float" "aset-int" "aset-long" "aset-short" "assert" |
|
398 |
"assoc" "assoc!" "assoc-in" "associative?" "atom" |
|
399 |
"await" "await-for" "await1" "bases" "bean" |
|
400 |
"bigdec" "bigint" "binding" "bit-and" "bit-and-not" |
|
401 |
"bit-clear" "bit-flip" "bit-not" "bit-or" "bit-set" |
|
402 |
"bit-shift-left" "bit-shift-right" "bit-test" "bit-xor" "boolean" |
|
403 |
"boolean-array" "booleans" "bound-fn" "bound-fn*" "butlast" |
|
404 |
"byte" "byte-array" "bytes" "case" "cast" "char" |
|
405 |
"char-array" "char-escape-string" "char-name-string" "char?" "chars" |
|
406 |
"chunk" "chunk-append" "chunk-buffer" "chunk-cons" "chunk-first" |
|
407 |
"chunk-next" "chunk-rest" "chunked-seq?" "class" "class?" |
|
408 |
"clear-agent-errors" "clojure-version" "coll?" "comment" "commute" |
|
409 |
"comp" "comparator" "compare" "compare-and-set!" "compile" |
|
410 |
"complement" "concat" "cond" "condp" "conj" |
|
411 |
"conj!" "cons" "constantly" "construct-proxy" "contains?" |
|
412 |
"count" "counted?" "create-ns" "create-struct" "cycle" |
|
413 |
"dec" "decimal?" "declare" "definline" "defmacro" |
|
414 |
"defmethod" "defmulti" "defn" "defn-" "defonce" |
|
415 |
"defstruct" "delay" "delay?" "deliver" "deref" |
|
416 |
"derive" "descendants" "destructure" "disj" "disj!" |
|
417 |
"dissoc" "dissoc!" "distinct" "distinct?" "doall" |
|
418 |
"doc" "dorun" "doseq" "dosync" "dotimes" |
|
419 |
"doto" "double" "double-array" "doubles" "drop" |
|
420 |
"drop-last" "drop-while" "empty" "empty?" "ensure" |
|
421 |
"enumeration-seq" "eval" "even?" "every?" |
|
422 |
"extend" "extend-protocol" "extend-type" "extends?" "extenders" |
|
423 |
"false?" "ffirst" "file-seq" "filter" "find" "find-doc" |
|
424 |
"find-ns" "find-var" "first" "flatten" "float" "float-array" |
|
425 |
"float?" "floats" "flush" "fn" "fn?" |
|
426 |
"fnext" "for" "force" "format" "future" |
|
427 |
"future-call" "future-cancel" "future-cancelled?" "future-done?" "future?" |
|
428 |
"gen-class" "gen-interface" "gensym" "get" "get-in" |
|
429 |
"get-method" "get-proxy-class" "get-thread-bindings" "get-validator" "hash" |
|
430 |
"hash-map" "hash-set" "identical?" "identity" "if-let" |
|
431 |
"if-not" "ifn?" "import" "in-ns" "inc" |
|
432 |
"init-proxy" "instance?" "int" "int-array" "integer?" |
|
433 |
"interleave" "intern" "interpose" "into" "into-array" |
|
434 |
"ints" "io!" "isa?" "iterate" "iterator-seq" |
|
435 |
"juxt" "key" "keys" "keyword" "keyword?" |
|
436 |
"last" "lazy-cat" "lazy-seq" "let" "letfn" |
|
437 |
"line-seq" "list" "list*" "list?" "load" |
|
438 |
"load-file" "load-reader" "load-string" "loaded-libs" "locking" |
|
439 |
"long" "long-array" "longs" "loop" "macroexpand" |
|
440 |
"macroexpand-1" "make-array" "make-hierarchy" "map" "map?" |
|
441 |
"mapcat" "max" "max-key" "memfn" "memoize" |
|
442 |
"merge" "merge-with" "meta" "method-sig" "methods" |
|
443 |
"min" "min-key" "mod" "name" "namespace" |
|
444 |
"neg?" "newline" "next" "nfirst" "nil?" |
|
445 |
"nnext" "not" "not-any?" "not-empty" "not-every?" |
|
446 |
"not=" "ns" "ns-aliases" "ns-imports" "ns-interns" |
|
447 |
"ns-map" "ns-name" "ns-publics" "ns-refers" "ns-resolve" |
|
448 |
"ns-unalias" "ns-unmap" "nth" "nthnext" "num" |
|
449 |
"number?" "odd?" "or" "parents" "partial" |
|
450 |
"partition" "pcalls" "peek" "persistent!" "pmap" |
|
451 |
"pop" "pop!" "pop-thread-bindings" "pos?" "pr" |
|
452 |
"pr-str" "prefer-method" "prefers" "primitives-classnames" "print" |
|
453 |
"print-ctor" "print-doc" "print-dup" "print-method" "print-namespace-doc" |
|
454 |
"print-simple" "print-special-doc" "print-str" "printf" "println" |
|
455 |
"println-str" "prn" "prn-str" "promise" "proxy" |
|
456 |
"proxy-call-with-super" "proxy-mappings" "proxy-name" "proxy-super" "push-thread-bindings" |
|
457 |
"pvalues" "quot" "rand" "rand-int" "range" |
|
458 |
"ratio?" "rational?" "rationalize" "re-find" "re-groups" |
|
459 |
"re-matcher" "re-matches" "re-pattern" "re-seq" "read" |
|
460 |
"read-line" "read-string" "reify" "reduce" "ref" "ref-history-count" |
|
461 |
"ref-max-history" "ref-min-history" "ref-set" "refer" "refer-clojure" |
|
462 |
"release-pending-sends" "rem" "remove" "remove-method" "remove-ns" |
|
463 |
"repeat" "repeatedly" "replace" "replicate" |
|
464 |
"require" "reset!" "reset-meta!" "resolve" "rest" |
|
465 |
"resultset-seq" "reverse" "reversible?" "rseq" "rsubseq" |
|
466 |
"satisfies?" "second" "select-keys" "send" "send-off" "seq" |
|
467 |
"seq?" "seque" "sequence" "sequential?" "set" |
|
468 |
"set-validator!" "set?" "short" "short-array" "shorts" |
|
469 |
"shutdown-agents" "slurp" "some" "sort" "sort-by" |
|
470 |
"sorted-map" "sorted-map-by" "sorted-set" "sorted-set-by" "sorted?" |
|
471 |
"special-form-anchor" "special-symbol?" "split-at" "split-with" "str" |
|
472 |
"stream?" "string?" "struct" "struct-map" "subs" |
|
473 |
"subseq" "subvec" "supers" "swap!" "symbol" |
|
474 |
"symbol?" "sync" "syntax-symbol-anchor" "take" "take-last" |
|
475 |
"take-nth" "take-while" "test" "the-ns" "time" |
|
476 |
"to-array" "to-array-2d" "trampoline" "transient" "tree-seq" |
|
477 |
"true?" "type" "unchecked-add" "unchecked-dec" "unchecked-divide" |
|
478 |
"unchecked-inc" "unchecked-multiply" "unchecked-negate" "unchecked-remainder" "unchecked-subtract" |
|
479 |
"underive" "unquote" "unquote-splicing" "update-in" "update-proxy" |
|
480 |
"use" "val" "vals" "var-get" "var-set" |
|
481 |
"var?" "vary-meta" "vec" "vector" "vector?" |
|
482 |
"when" "when-first" "when-let" "when-not" "while" |
|
483 |
"with-bindings" "with-bindings*" "with-in-str" "with-loading-context" "with-local-vars" |
|
484 |
"with-meta" "with-open" "with-out-str" "with-precision" "xml-seq" "zipmap" |
|
485 |
) t) |
|
486 |
"\\>") |
|
487 |
1 font-lock-variable-name-face) |
|
488 |
;; (fn name? args ...) |
|
489 |
(,(concat "(\\(?:clojure.core/\\)?\\(fn\\)[ \t]+" |
|
490 |
;; Possibly type |
|
491 |
"\\(?:#?^\\sw+[ \t]*\\)?" |
|
492 |
;; Possibly name |
|
493 |
"\\(\\sw+\\)?" ) |
|
494 |
(1 font-lock-keyword-face) |
|
495 |
(2 font-lock-function-name-face nil t)) |
|
496 |
;;Other namespaces in clojure.jar |
|
497 |
(,(concat |
|
498 |
"(\\(?:\.*/\\)?" |
|
499 |
(regexp-opt |
|
500 |
'(;; clojure.inspector |
|
501 |
"atom?" "collection-tag" "get-child" "get-child-count" "inspect" |
|
502 |
"inspect-table" "inspect-tree" "is-leaf" "list-model" "list-provider" |
|
503 |
;; clojure.main |
|
504 |
"load-script" "main" "repl" "repl-caught" "repl-exception" |
|
505 |
"repl-prompt" "repl-read" "skip-if-eol" "skip-whitespace" "with-bindings" |
|
506 |
;; clojure.set |
|
507 |
"difference" "index" "intersection" "join" "map-invert" |
|
508 |
"project" "rename" "rename-keys" "select" "union" |
|
509 |
;; clojure.stacktrace |
|
510 |
"e" "print-cause-trace" "print-stack-trace" "print-throwable" "print-trace-element" |
|
511 |
;; clojure.template |
|
512 |
"do-template" "apply-template" |
|
513 |
;; clojure.test |
|
514 |
"*initial-report-counters*" "*load-tests*" "*report-counters*" "*stack-trace-depth*" "*test-out*" |
|
515 |
"*testing-contexts*" "*testing-vars*" "are" "assert-any" "assert-expr" |
|
516 |
"assert-predicate" "compose-fixtures" "deftest" "deftest-" "file-position" |
|
517 |
"function?" "get-possibly-unbound-var" "inc-report-counter" "is" "join-fixtures" |
|
518 |
"report" "run-all-tests" "run-tests" "set-test" "successful?" |
|
519 |
"test-all-vars" "test-ns" "test-var" "testing" "testing-contexts-str" |
|
520 |
"testing-vars-str" "try-expr" "use-fixtures" "with-test" "with-test-out" |
|
521 |
;; clojure.walk |
|
522 |
"keywordize-keys" "macroexpand-all" "postwalk" "postwalk-demo" "postwalk-replace" |
|
523 |
"prewalk" "prewalk-demo" "prewalk-replace" "stringify-keys" "walk" |
|
524 |
;; clojure.xml |
|
525 |
"*current*" "*sb*" "*stack*" "*state*" "attrs" |
|
526 |
"content" "content-handler" "element" "emit" "emit-element" |
|
527 |
;; clojure.zip |
|
528 |
"append-child" "branch?" "children" "down" "edit" |
|
529 |
"end?" "insert-child" "insert-left" "insert-right" "left" |
|
530 |
"leftmost" "lefts" "make-node" "next" "node" |
|
531 |
"path" "prev" "remove" "replace" "right" |
|
532 |
"rightmost" "rights" "root" "seq-zip" "up" |
|
533 |
) t) |
|
534 |
"\\>") |
|
535 |
1 font-lock-type-face) |
|
536 |
;; Constant values (keywords), including as metadata e.g. ^:static |
|
537 |
("\\<^?:\\(\\sw\\|#\\)+\\>" 0 font-lock-constant-face) |
|
538 |
;; Meta type annotation #^Type or ^Type |
|
539 |
("#?^\\sw+" 0 font-lock-type-face) |
|
540 |
("\\<io\\!\\>" 0 font-lock-warning-face) |
|
541 |
|
|
542 |
;;Java interop highlighting |
|
543 |
("\\<\\.[a-z][a-zA-Z0-9]*\\>" 0 font-lock-preprocessor-face) ;; .foo .barBaz .qux01 |
|
544 |
("\\<[A-Z][a-zA-Z0-9]*/[a-zA-Z0-9/$_]+\\>" 0 font-lock-preprocessor-face) ;; Foo Bar$Baz Qux_ |
|
545 |
("\\<[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 |
|
546 |
("[a-z]*[A-Z]+[a-z][a-zA-Z0-9$]*\\>" 0 font-lock-preprocessor-face) ;; fooBar |
|
547 |
("\\<[A-Z][a-zA-Z0-9$]*\\.\\>" 0 font-lock-preprocessor-face))) ;; Foo. BarBaz. Qux$Quux. Corge9. |
|
548 |
|
|
549 |
|
|
550 |
"Default expressions to highlight in Clojure mode.") |
|
551 |
|
|
552 |
;; Docstring positions |
|
553 |
(put 'defn 'clojure-doc-string-elt 2) |
|
554 |
(put 'defn- 'clojure-doc-string-elt 2) |
|
555 |
(put 'defmulti 'clojure-doc-string-elt 2) |
|
556 |
(put 'defmacro 'clojure-doc-string-elt 2) |
|
557 |
(put 'definline 'clojure-doc-string-elt 2) |
|
558 |
(put 'defprotocol 'clojure-doc-string-elt 2) |
|
559 |
|
|
560 |
;; Docstring positions - contrib |
|
561 |
(put 'defalias 'clojure-doc-string-elt 3) |
|
562 |
(put 'defmacro- 'clojure-doc-string-elt 2) |
|
563 |
(put 'defn-memo 'clojure-doc-string-elt 2) |
|
564 |
(put 'defnk 'clojure-doc-string-elt 2) |
|
565 |
(put 'defonce- 'clojure-doc-string-elt 3) |
|
566 |
(put 'defunbound 'clojure-doc-string-elt 2) |
|
567 |
(put 'defunbound- 'clojure-doc-string-elt 2) |
|
568 |
(put 'defvar 'clojure-doc-string-elt 3) |
|
569 |
(put 'defvar- 'clojure-doc-string-elt 3) |
|
570 |
|
|
571 |
|
|
572 |
|
|
573 |
(defun clojure-forward-sexp (n) |
|
574 |
"Treat record literals like #user.Foo[1] and #user.Foo{:size 1} |
|
575 |
as a single sexp so that slime will send them properly. Arguably |
|
576 |
this behavior is unintuitive for the user pressing (eg) C-M-f |
|
577 |
himself, but since these are single objects I think it's right." |
|
578 |
(let ((dir (if (> n 0) 1 -1)) |
|
579 |
(forward-sexp-function nil)) ; force the built-in version |
|
580 |
(while (not (zerop n)) |
|
581 |
(forward-sexp dir) |
|
582 |
(when (save-excursion ; move back to see if we're in a record literal |
|
583 |
(and |
|
584 |
(condition-case nil |
|
585 |
(progn (backward-sexp) 't) |
|
586 |
('scan-error nil)) |
|
587 |
(looking-at "#\\w"))) |
|
588 |
(forward-sexp dir)) ; if so, jump over it |
|
589 |
(setq n (- n dir))))) |
|
590 |
|
|
591 |
(defun clojure-indent-function (indent-point state) |
|
592 |
"This function is the normal value of the variable `lisp-indent-function'. |
|
593 |
It is used when indenting a line within a function call, to see if the |
|
594 |
called function says anything special about how to indent the line. |
|
595 |
|
|
596 |
INDENT-POINT is the position where the user typed TAB, or equivalent. |
|
597 |
Point is located at the point to indent under (for default indentation); |
|
598 |
STATE is the `parse-partial-sexp' state for that position. |
|
599 |
|
|
600 |
If the current line is in a call to a Lisp function |
|
601 |
which has a non-nil property `lisp-indent-function', |
|
602 |
that specifies how to do the indentation. The property value can be |
|
603 |
* `defun', meaning indent `defun'-style; |
|
604 |
* an integer N, meaning indent the first N arguments specially |
|
605 |
like ordinary function arguments and then indent any further |
|
606 |
arguments like a body; |
|
607 |
* a function to call just as this function was called. |
|
608 |
If that function returns nil, that means it doesn't specify |
|
609 |
the indentation. |
|
610 |
|
|
611 |
This function also returns nil meaning don't specify the indentation." |
|
612 |
(let ((normal-indent (current-column))) |
|
613 |
(goto-char (1+ (elt state 1))) |
|
614 |
(parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t) |
|
615 |
(if (and (elt state 2) |
|
616 |
(not (looking-at "\\sw\\|\\s_"))) |
|
617 |
;; car of form doesn't seem to be a symbol |
|
618 |
(progn |
|
619 |
(if (not (> (save-excursion (forward-line 1) (point)) |
|
620 |
calculate-lisp-indent-last-sexp)) |
|
621 |
(progn (goto-char calculate-lisp-indent-last-sexp) |
|
622 |
(beginning-of-line) |
|
623 |
(parse-partial-sexp (point) |
|
624 |
calculate-lisp-indent-last-sexp 0 t))) |
|
625 |
;; Indent under the list or under the first sexp on the same |
|
626 |
;; line as calculate-lisp-indent-last-sexp. Note that first |
|
627 |
;; thing on that line has to be complete sexp since we are |
|
628 |
;; inside the innermost containing sexp. |
|
629 |
(backward-prefix-chars) |
|
630 |
(if (and (eq (char-after (point)) ?\[) |
|
631 |
(eq (char-after (elt state 1)) ?\()) |
|
632 |
(+ (current-column) 2) ;; this is probably inside a defn |
|
633 |
(current-column))) |
|
634 |
(let ((function (buffer-substring (point) |
|
635 |
(progn (forward-sexp 1) (point)))) |
|
636 |
(open-paren (elt state 1)) |
|
637 |
method) |
|
638 |
(setq method (get (intern-soft function) 'clojure-indent-function)) |
|
639 |
|
|
640 |
(cond ((member (char-after open-paren) '(?\[ ?\{)) |
|
641 |
(goto-char open-paren) |
|
642 |
(1+ (current-column))) |
|
643 |
((or (eq method 'defun) |
|
644 |
(and (null method) |
|
645 |
(> (length function) 3) |
|
646 |
(string-match "\\`\\(?:\\S +/\\)?def\\|with-" |
|
647 |
function))) |
|
648 |
(lisp-indent-defform state indent-point)) |
|
649 |
|
|
650 |
((integerp method) |
|
651 |
(lisp-indent-specform method state |
|
652 |
indent-point normal-indent)) |
|
653 |
(method |
|
654 |
(funcall method indent-point state)) |
|
655 |
(clojure-mode-use-backtracking-indent |
|
656 |
(clojure-backtracking-indent |
|
657 |
indent-point state normal-indent))))))) |
|
658 |
|
|
659 |
(defun clojure-backtracking-indent (indent-point state normal-indent) |
|
660 |
"Experimental backtracking support. Will upwards in an sexp to |
|
661 |
check for contextual indenting." |
|
662 |
(let (indent (path) (depth 0)) |
|
663 |
(goto-char (elt state 1)) |
|
664 |
(while (and (not indent) |
|
665 |
(< depth clojure-max-backtracking)) |
|
666 |
(let ((containing-sexp (point))) |
|
667 |
(parse-partial-sexp (1+ containing-sexp) indent-point 1 t) |
|
668 |
(when (looking-at "\\sw\\|\\s_") |
|
669 |
(let* ((start (point)) |
|
670 |
(fn (buffer-substring start (progn (forward-sexp 1) (point)))) |
|
671 |
(meth (get (intern-soft fn) 'clojure-backtracking-indent))) |
|
672 |
(let ((n 0)) |
|
673 |
(when (< (point) indent-point) |
|
674 |
(condition-case () |
|
675 |
(progn |
|
676 |
(forward-sexp 1) |
|
677 |
(while (< (point) indent-point) |
|
678 |
(parse-partial-sexp (point) indent-point 1 t) |
|
679 |
(incf n) |
|
680 |
(forward-sexp 1))) |
|
681 |
(error nil))) |
|
682 |
(push n path)) |
|
683 |
(when meth |
|
684 |
(let ((def meth)) |
|
685 |
(dolist (p path) |
|
686 |
(if (and (listp def) |
|
687 |
(< p (length def))) |
|
688 |
(setq def (nth p def)) |
|
689 |
(if (listp def) |
|
690 |
(setq def (car (last def))) |
|
691 |
(setq def nil)))) |
|
692 |
(goto-char (elt state 1)) |
|
693 |
(when def |
|
694 |
(setq indent (+ (current-column) def))))))) |
|
695 |
(goto-char containing-sexp) |
|
696 |
(condition-case () |
|
697 |
(progn |
|
698 |
(backward-up-list 1) |
|
699 |
(incf depth)) |
|
700 |
(error (setq depth clojure-max-backtracking))))) |
|
701 |
indent)) |
|
702 |
|
|
703 |
;; clojure backtracking indent is experimental and the format for these |
|
704 |
;; entries are subject to change |
|
705 |
(put 'implement 'clojure-backtracking-indent '(4 (2))) |
|
706 |
(put 'letfn 'clojure-backtracking-indent '((2) 2)) |
|
707 |
(put 'proxy 'clojure-backtracking-indent '(4 4 (2))) |
|
708 |
(put 'reify 'clojure-backtracking-indent '((2))) |
|
709 |
(put 'deftype 'clojure-backtracking-indent '(4 4 (2))) |
|
710 |
(put 'defrecord 'clojure-backtracking-indent '(4 4 (2))) |
|
711 |
(put 'defprotocol 'clojure-backtracking-indent '(4 (2))) |
|
712 |
(put 'extend-type 'clojure-backtracking-indent '(4 (2))) |
|
713 |
(put 'extend-protocol 'clojure-backtracking-indent '(4 (2))) |
|
714 |
|
|
715 |
(defun put-clojure-indent (sym indent) |
|
716 |
(put sym 'clojure-indent-function indent)) |
|
717 |
|
|
718 |
(defmacro define-clojure-indent (&rest kvs) |
|
719 |
`(progn |
|
720 |
,@(mapcar (lambda (x) `(put-clojure-indent |
|
721 |
(quote ,(first x)) ,(second x))) kvs))) |
|
722 |
|
|
723 |
(defun add-custom-clojure-indents (name value) |
|
724 |
(setq clojure-defun-indents value) |
|
725 |
(mapcar (lambda (x) |
|
726 |
(put-clojure-indent x 'defun)) |
|
727 |
value)) |
|
728 |
|
|
729 |
(defcustom clojure-defun-indents nil |
|
730 |
"List of symbols to give defun-style indentation to in Clojure |
|
731 |
code, in addition to those that are built-in. You can use this to |
|
732 |
get emacs to indent your own macros the same as it does the |
|
733 |
built-ins like with-open. To set manually from lisp code, |
|
734 |
use (put-clojure-indent 'some-symbol 'defun)." |
|
735 |
:type '(repeat symbol) |
|
736 |
:group 'clojure-mode |
|
737 |
:set 'add-custom-clojure-indents) |
|
738 |
|
|
739 |
(define-clojure-indent |
|
740 |
;; built-ins |
|
741 |
(ns 1) |
|
742 |
(fn 'defun) |
|
743 |
(def 'defun) |
|
744 |
(defn 'defun) |
|
745 |
(bound-fn 'defun) |
|
746 |
(if 1) |
|
747 |
(if-not 1) |
|
748 |
(case 1) |
|
749 |
(condp 2) |
|
750 |
(when 1) |
|
751 |
(while 1) |
|
752 |
(when-not 1) |
|
753 |
(when-first 1) |
|
754 |
(do 0) |
|
755 |
(future 0) |
|
756 |
(comment 0) |
|
757 |
(doto 1) |
|
758 |
(locking 1) |
|
759 |
(proxy 2) |
|
760 |
(with-open 1) |
|
761 |
(with-precision 1) |
|
762 |
(with-local-vars 1) |
|
763 |
|
|
764 |
(reify 'defun) |
|
765 |
(deftype 2) |
|
766 |
(defrecord 2) |
|
767 |
(defprotocol 1) |
|
768 |
(extend 1) |
|
769 |
(extend-protocol 1) |
|
770 |
(extend-type 1) |
|
771 |
|
|
772 |
(try 0) |
|
773 |
(catch 2) |
|
774 |
(finally 0) |
|
775 |
|
|
776 |
;; binding forms |
|
777 |
(let 1) |
|
778 |
(letfn 1) |
|
779 |
(binding 1) |
|
780 |
(loop 1) |
|
781 |
(for 1) |
|
782 |
(doseq 1) |
|
783 |
(dotimes 1) |
|
784 |
(when-let 1) |
|
785 |
(if-let 1) |
|
786 |
|
|
787 |
;; data structures |
|
788 |
(defstruct 1) |
|
789 |
(struct-map 1) |
|
790 |
(assoc 1) |
|
791 |
|
|
792 |
(defmethod 'defun) |
|
793 |
|
|
794 |
;; clojure.test |
|
795 |
(testing 1) |
|
796 |
(deftest 'defun) |
|
797 |
|
|
798 |
;; contrib |
|
799 |
(handler-case 1) |
|
800 |
(handle 1) |
|
801 |
(dotrace 1) |
|
802 |
(deftrace 'defun)) |
|
803 |
|
|
804 |
|
|
805 |
|
|
806 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
807 |
;; |
|
808 |
;; Better docstring filling for clojure-mode |
|
809 |
;; |
|
810 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
811 |
|
|
812 |
(defun clojure-string-start () |
|
813 |
"Return the position of the \" that begins the string at point." |
|
814 |
(save-excursion |
|
815 |
(save-match-data |
|
816 |
;; Find a quote that appears immediately after whitespace, |
|
817 |
;; beginning of line, or an open paren, brace, or bracket |
|
818 |
(re-search-backward "\\(\\s-\\|^\\|(\\|\\[\\|{\\)\\(\"\\)") |
|
819 |
(match-beginning 2)))) |
|
820 |
|
|
821 |
(defun clojure-char-at-point () |
|
822 |
"Return the char at point or nil if at buffer end." |
|
823 |
(when (not (= (point) (point-max))) |
|
824 |
(buffer-substring-no-properties (point) (1+ (point))))) |
|
825 |
|
|
826 |
(defun clojure-char-before-point () |
|
827 |
"Return the char before point or nil if at buffer beginning." |
|
828 |
(when (not (= (point) (point-min))) |
|
829 |
(buffer-substring-no-properties (point) (1- (point))))) |
|
830 |
|
|
831 |
;; TODO: Deal with the fact that when point is exactly at the |
|
832 |
;; beginning of a string, it thinks that is the end. |
|
833 |
(defun clojure-string-end () |
|
834 |
"Return the position of the \" that ends the string at point. |
|
835 |
|
|
836 |
Note that point must be inside the string - if point is |
|
837 |
positioned at the opening quote, incorrect results will be |
|
838 |
returned." |
|
839 |
(save-excursion |
|
840 |
(save-match-data |
|
841 |
;; If we're at the end of the string, just return point. |
|
842 |
(if (and (string= (clojure-char-at-point) "\"") |
|
843 |
(not (string= (clojure-char-before-point) "\\"))) |
|
844 |
(point) |
|
845 |
;; We don't want to get screwed by starting out at the |
|
846 |
;; backslash in an escaped quote. |
|
847 |
(when (string= (clojure-char-at-point) "\\") |
|
848 |
(backward-char)) |
|
849 |
;; Look for a quote not preceeded by a backslash |
|
850 |
(re-search-forward "[^\\]\\\(\\\"\\)") |
|
851 |
(match-beginning 1))))) |
|
852 |
|
|
853 |
(defun clojure-docstring-start+end-points () |
|
854 |
"Return the start and end points of the string at point as a cons." |
|
855 |
(if (and (fboundp 'paredit-string-start+end-points) paredit-mode) |
|
856 |
(paredit-string-start+end-points) |
|
857 |
(cons (clojure-string-start) (clojure-string-end)))) |
|
858 |
|
|
859 |
(defun clojure-mark-string () |
|
860 |
"Mark the string at point." |
|
861 |
(interactive) |
|
862 |
(goto-char (clojure-string-start)) |
|
863 |
(forward-char) |
|
864 |
(set-mark (clojure-string-end))) |
|
865 |
|
|
866 |
(defun clojure-fill-docstring (&optional argument) |
|
867 |
"Fill the definition that the point is on appropriate for Clojure. |
|
868 |
|
|
869 |
Fills so that every paragraph has a minimum of two initial spaces, |
|
870 |
with the exception of the first line. Fill margins are taken from |
|
871 |
paragraph start, so a paragraph that begins with four spaces will |
|
872 |
remain indented by four spaces after refilling." |
|
873 |
(interactive "P") |
|
874 |
(if (and (fboundp 'paredit-in-string-p) paredit-mode) |
|
875 |
(unless (paredit-in-string-p) |
|
876 |
(error "Must be inside a string"))) |
|
877 |
;; Oddly, save-excursion doesn't do a good job of preserving point. |
|
878 |
;; It's probably because we delete the string and then re-insert it. |
|
879 |
(let ((old-point (point))) |
|
880 |
(save-restriction |
|
881 |
(save-excursion |
|
882 |
(let* ((string-region (clojure-docstring-start+end-points)) |
|
883 |
(string-start (1+ (car string-region))) |
|
884 |
(string-end (cdr string-region)) |
|
885 |
(string (buffer-substring-no-properties (1+ (car string-region)) |
|
886 |
(cdr string-region)))) |
|
887 |
(delete-region string-start string-end) |
|
888 |
(insert |
|
889 |
(with-temp-buffer |
|
890 |
(insert string) |
|
891 |
(let ((left-margin 2)) |
|
892 |
(delete-trailing-whitespace) |
|
893 |
(fill-region (point-min) (point-max)) |
|
894 |
(buffer-substring-no-properties (+ 2 (point-min)) (point-max)))))))) |
|
895 |
(goto-char old-point))) |
|
896 |
|
|
897 |
|
|
898 |
|
|
899 |
(defconst clojure-namespace-name-regex |
|
900 |
(rx line-start |
|
901 |
"(" |
|
902 |
(zero-or-one (group (regexp "clojure.core/"))) |
|
903 |
(zero-or-one (submatch "in-")) |
|
904 |
"ns" |
|
905 |
(zero-or-one "+") |
|
906 |
(one-or-more (any whitespace "\n")) |
|
907 |
(zero-or-more (or (submatch (zero-or-one "#") |
|
908 |
"^{" |
|
909 |
(zero-or-more (not (any "}"))) |
|
910 |
"}") |
|
911 |
(zero-or-more "^:" |
|
912 |
(one-or-more (not (any whitespace))))) |
|
913 |
(one-or-more (any whitespace "\n"))) |
|
914 |
;; why is this here? oh (in-ns 'foo) or (ns+ :user) |
|
915 |
(zero-or-one (any ":'")) |
|
916 |
(group (one-or-more (not (any "()\"" whitespace))) word-end))) |
|
917 |
|
|
918 |
;; for testing clojure-namespace-name-regex, you can evaluate this code and make |
|
919 |
;; sure foo (or whatever the namespace name is) shows up in results. some of |
|
920 |
;; these currently fail. |
|
921 |
;; (mapcar (lambda (s) (let ((n (string-match clojure-namespace-name-regex s))) |
|
922 |
;; (if n (match-string 4 s)))) |
|
923 |
;; '("(ns foo)" |
|
924 |
;; "(ns |
|
925 |
;; foo)" |
|
926 |
;; "(ns foo.baz)" |
|
927 |
;; "(ns ^:bar foo)" |
|
928 |
;; "(ns ^:bar ^:baz foo)" |
|
929 |
;; "(ns ^{:bar true} foo)" |
|
930 |
;; "(ns #^{:bar true} foo)" |
|
931 |
;; "(ns #^{:fail {}} foo)" |
|
932 |
;; "(ns ^{:fail2 {}} foo.baz)" |
|
933 |
;; "(ns ^{} foo)" |
|
934 |
;; "(ns ^{:skip-wiki true} |
|
935 |
;; aleph.netty |
|
936 |
;; " |
|
937 |
;; "(ns |
|
938 |
;; foo)" |
|
939 |
;; "foo")) |
|
940 |
|
|
941 |
|
|
942 |
|
|
943 |
(defun clojure-insert-ns-form () |
|
944 |
(interactive) |
|
945 |
(goto-char (point-min)) |
|
946 |
(let* ((rel (car (last (split-string buffer-file-name "src/\\|test/")))) |
|
947 |
(relative (car (split-string rel "\\.clj"))) |
|
948 |
(segments (split-string relative "/"))) |
|
949 |
(insert (format "(ns %s)" (mapconcat #'identity segments "."))))) |
|
950 |
|
|
951 |
|
|
952 |
;;; Slime help |
|
953 |
|
|
954 |
(defvar clojure-project-root-file "project.clj") |
|
955 |
|
|
956 |
;; Pipe to $SHELL to work around mackosecks GUI Emacs $PATH issues. |
|
957 |
(defcustom clojure-swank-command |
|
958 |
(if (or (locate-file "lein" exec-path) (locate-file "lein.bat" exec-path)) |
|
959 |
"lein jack-in %s" |
|
960 |
"echo \"lein jack-in %s\" | $SHELL -l") |
|
961 |
"The command used to start swank via clojure-jack-in. |
|
962 |
For remote swank it is lein must be in your PATH and the remote |
|
963 |
proc is launched via sh rather than bash, so it might be necessary |
|
964 |
to specific the full path to it. The argument is the port to connect on. |
|
965 |
Localhost is assumed." |
|
966 |
:type 'string |
|
967 |
:group 'clojure-mode) |
|
968 |
|
|
969 |
(defcustom clojure-generate-remote-swank-command-function |
|
970 |
'clojure-generate-remote-swank-command-ssh-tunnel |
|
971 |
"A function that is called to determine the swank command that |
|
972 |
`clojure-jack-in` will execute and the hostname/port that slime |
|
973 |
should connect to for remote projects that are opened via tramp. |
|
974 |
|
|
975 |
The arguments are dir, hostname, and port. The return value should be an `alist` of the form |
|
976 |
(:cmd \"command string\" :hostname \"hostname\" :port 1234)" |
|
977 |
:type 'function |
|
978 |
:group 'clojure-mode) |
|
979 |
|
|
980 |
(defun clojure-generate-local-swank-command-default (dir hostname port) |
|
981 |
(if (not (string-equal "localhost" hostname)) |
|
982 |
(error (concat |
|
983 |
"If you need to jack-in to remote projects/jvms over tramp, " |
|
984 |
"you need to define a custom `clojure-generate-swank-command-function`")) |
|
985 |
(list :cmd (format clojure-swank-command port) |
|
986 |
:hostname hostname |
|
987 |
:port port))) |
|
988 |
|
|
989 |
(defun clojure-generate-remote-swank-command-ssh-tunnel (dir hostname port) |
|
990 |
(destructuring-bind (_method user host localname) |
|
991 |
(append (tramp-dissect-file-name dir) nil) |
|
992 |
(list :cmd (format-spec |
|
993 |
"ssh -L %p:localhost:%p -l '%u' '%h' 'cd \'%d\'; lein jack-in \'%p\''" |
|
994 |
`((?p . ,port) |
|
995 |
(?h . ,host) |
|
996 |
(?u . ,(or user (getenv "USER"))) |
|
997 |
(?d . ,localname))) |
|
998 |
:hostname "localhost" |
|
999 |
:port port))) |
|
1000 |
|
|
1001 |
(defun clojure-generate-swank-cmd (dir hostname port) |
|
1002 |
(if (file-remote-p dir) |
|
1003 |
(if (functionp clojure-generate-remote-swank-command-function) |
|
1004 |
(funcall clojure-generate-remote-swank-command-function dir hostname port) |
|
1005 |
(error (concat |
|
1006 |
"If you need to jack-in to remote projects/jvms over tramp " |
|
1007 |
"you need to define a custom `clojure-generate-remote-swank-command-function`"))) |
|
1008 |
(clojure-generate-local-swank-command-default dir hostname port))) |
|
1009 |
|
|
1010 |
(defun clojure-jack-in-sentinel (process event) |
|
1011 |
(let ((debug-on-error t)) |
|
1012 |
(error "Could not start swank server: %s" |
|
1013 |
(let ((b (process-buffer process))) |
|
1014 |
(if (and b (buffer-live-p b)) |
|
1015 |
(with-current-buffer b |
|
1016 |
(buffer-substring (point-min) (point-max)))))))) |
|
1017 |
|
|
1018 |
(defun clojure-eval-bootstrap-region (process) |
|
1019 |
"Eval only the elisp in between the markers." |
|
1020 |
(with-current-buffer (process-buffer process) |
|
1021 |
(save-excursion |
|
1022 |
(goto-char 0) |
|
1023 |
(search-forward ";;; Bootstrapping bundled version of SLIME") |
|
1024 |
(let ((begin (point))) |
|
1025 |
(when (not (search-forward ";;; Done bootstrapping." nil t)) |
|
1026 |
;; fall back to possibly-ambiguous string if above isn't found |
|
1027 |
(search-forward "(run-hooks 'slime-load-hook)")) |
|
1028 |
(eval-region begin (point)))))) |
|
1029 |
|
|
1030 |
(defun clojure-kill-swank-buffer (swank-buffer-name) |
|
1031 |
(when (get-buffer swank-buffer-name) |
|
1032 |
(let ((process (get-buffer-process (get-buffer swank-buffer-name)))) |
|
1033 |
(if process |
|
1034 |
(set-process-query-on-exit-flag process nil)) |
|
1035 |
(kill-buffer swank-buffer-name)))) |
|
1036 |
|
|
1037 |
(defun clojure-generate-swank-connection-name (dir hostname) |
|
1038 |
"swank") |
|
1039 |
|
|
1040 |
(defun clojure-jack-in-start-process (swank-connection-name swank-buffer-name dir hostname) |
|
1041 |
;; The buffer has to be created before the proc if |
|
1042 |
;; `start-file-process-shell-command` is used. It doesn't hurt to do |
|
1043 |
;; it now even if `start-process-shell-command` is used: |
|
1044 |
(get-buffer-create swank-buffer-name) |
|
1045 |
|
|
1046 |
(let ((port (- 65535 (mod (caddr (current-time)) 4096)))) |
|
1047 |
(destructuring-bind (&key cmd hostname port) |
|
1048 |
(clojure-generate-swank-cmd dir hostname port) |
|
1049 |
(lexical-let* ((proc (start-process-shell-command |
|
1050 |
;; this command runs locally |
|
1051 |
;; `start-file-process-shell-command` would |
|
1052 |
;; run remote for tramp buffers |
|
1053 |
swank-connection-name |
|
1054 |
swank-buffer-name |
|
1055 |
cmd)) |
|
1056 |
(dir dir) |
|
1057 |
(hostname hostname) |
|
1058 |
(port port) |
|
1059 |
(connect-callback (lambda () (slime-connect hostname port)))) |
|
1060 |
(set-process-sentinel proc 'clojure-jack-in-sentinel) |
|
1061 |
(set-process-query-on-exit-flag proc nil) |
|
1062 |
(set-process-filter proc |
|
1063 |
(lambda (process output) |
|
1064 |
(with-current-buffer (process-buffer process) |
|
1065 |
(insert output)) |
|
1066 |
(when (string-match "proceed to jack in" output) |
|
1067 |
(clojure-eval-bootstrap-region process) |
|
1068 |
(with-current-buffer |
|
1069 |
;; this block is an attempt to avoid |
|
1070 |
;; creating duplicate repl windows |
|
1071 |
(or |
|
1072 |
(get-buffer "*slime-repl clojure*") |
|
1073 |
(get-buffer "*slime-repl nil*") |
|
1074 |
(current-buffer)) |
|
1075 |
(funcall connect-callback) |
|
1076 |
(when (string-match "slime-repl" (buffer-name)) |
|
1077 |
;; this is most likely an old repl |
|
1078 |
;; buffer that existed prior to the |
|
1079 |
;; jack-in call. |
|
1080 |
(setq default-directory dir) |
|
1081 |
(goto-char (point-max)))) |
|
1082 |
(set-process-sentinel process nil) |
|
1083 |
(set-process-filter process nil)))))))) |
|
1084 |
|
|
1085 |
;;;###autoload |
|
1086 |
(defun clojure-jack-in () |
|
1087 |
(interactive) |
|
1088 |
(setq slime-net-coding-system 'utf-8-unix) |
|
1089 |
(let* ((dir default-directory) |
|
1090 |
(hostname (if (file-remote-p default-directory) |
|
1091 |
tramp-current-host "localhost")) |
|
1092 |
(connection-name (clojure-generate-swank-connection-name dir hostname)) |
|
1093 |
(swank-buffer-name (format "*%s*" connection-name))) |
|
1094 |
|
|
1095 |
(when (and (functionp 'slime-disconnect) |
|
1096 |
(slime-current-connection) |
|
1097 |
;; TODO: ask for permission once jack-in supports multiple connections |
|
1098 |
;; (and (interactive-p) (y-or-n-p "Close old connections first? ")) |
|
1099 |
) |
|
1100 |
(slime-disconnect)) |
|
1101 |
(clojure-kill-swank-buffer swank-buffer-name) |
|
1102 |
(clojure-jack-in-start-process connection-name swank-buffer-name dir hostname)) |
|
1103 |
(message "Starting swank server...")) |
|
1104 |
|
|
1105 |
(defun clojure-find-ns () |
|
1106 |
(let ((regexp clojure-namespace-name-regex)) |
|
1107 |
(save-excursion |
|
1108 |
(when (or (re-search-backward regexp nil t) |
|
1109 |
(re-search-forward regexp nil t)) |
|
1110 |
(match-string-no-properties 4))))) |
|
1111 |
|
|
1112 |
(defalias 'clojure-find-package 'clojure-find-ns) |
|
1113 |
|
|
1114 |
(defun clojure-enable-slime () |
|
1115 |
(slime-mode t) |
|
1116 |
(set (make-local-variable 'slime-find-buffer-package-function) |
|
1117 |
'clojure-find-ns)) |
|
1118 |
|
|
1119 |
;;;###autoload |
|
1120 |
(defun clojure-enable-slime-on-existing-buffers () |
|
1121 |
(interactive) |
|
1122 |
(add-hook 'clojure-mode-hook 'clojure-enable-slime) |
|
1123 |
(save-window-excursion |
|
1124 |
(dolist (buffer (buffer-list)) |
|
1125 |
(with-current-buffer buffer |
|
1126 |
(when (eq major-mode 'clojure-mode) |
|
1127 |
(clojure-enable-slime)))))) |
|
1128 |
|
|
1129 |
;; Test navigation: |
|
1130 |
(defun clojure-in-tests-p () |
|
1131 |
(or (string-match-p "test\." (clojure-find-ns)) |
|
1132 |
(string-match-p "/test" (buffer-file-name)))) |
|
1133 |
|
|
1134 |
(defun clojure-underscores-for-hyphens (namespace) |
|
1135 |
(replace-regexp-in-string "-" "_" namespace)) |
|
1136 |
|
|
1137 |
(defun clojure-test-for (namespace) |
|
1138 |
(let* ((namespace (clojure-underscores-for-hyphens namespace)) |
|
1139 |
(segments (split-string namespace "\\.")) |
|
1140 |
(before (subseq segments 0 clojure-test-ns-segment-position)) |
|
1141 |
(after (subseq segments clojure-test-ns-segment-position)) |
|
1142 |
(test-segments (append before (list "test") after))) |
|
1143 |
(mapconcat 'identity test-segments "/"))) |
|
1144 |
|
|
1145 |
(defun clojure-jump-to-test () |
|
1146 |
"Jump from implementation file to test." |
|
1147 |
(interactive) |
|
1148 |
(find-file (format "%stest/%s.clj" |
|
1149 |
(file-name-as-directory |
|
1150 |
(locate-dominating-file buffer-file-name "src/")) |
|
1151 |
(clojure-test-for (clojure-find-ns))))) |
|
1152 |
|
|
1153 |
(defun clojure-jump-between-tests-and-code () |
|
1154 |
(interactive) |
|
1155 |
(if (clojure-in-tests-p) |
|
1156 |
(clojure-test-jump-to-implementation) |
|
1157 |
(clojure-jump-to-test))) |
|
1158 |
|
|
1159 |
;;; slime filename translation for tramp |
|
1160 |
(defun clojure-slime-tramp-local-filename (f) |
|
1161 |
(if (file-remote-p f) |
|
1162 |
(tramp-file-name-localname |
|
1163 |
(tramp-dissect-file-name f)) |
|
1164 |
f)) |
|
1165 |
|
|
1166 |
(defun clojure-slime-tramp-remote-filename (f) |
|
1167 |
(if (file-remote-p default-directory) |
|
1168 |
(tramp-make-tramp-file-name |
|
1169 |
(tramp-file-name-method |
|
1170 |
(tramp-dissect-file-name default-directory)) |
|
1171 |
(tramp-file-name-user |
|
1172 |
(tramp-dissect-file-name default-directory)) |
|
1173 |
(tramp-file-name-host |
|
1174 |
(tramp-dissect-file-name default-directory)) |
|
1175 |
f) |
|
1176 |
f)) |
|
1177 |
|
|
1178 |
(defun clojure-slime-remote-file-name-hook () |
|
1179 |
(setq slime-from-lisp-filename-function |
|
1180 |
'clojure-slime-tramp-remote-filename) |
|
1181 |
(setq slime-to-lisp-filename-function |
|
1182 |
'clojure-slime-tramp-local-filename)) |
|
1183 |
|
|
1184 |
(add-hook 'slime-connected-hook 'clojure-slime-remote-file-name-hook) |
|
1185 |
|
|
1186 |
;;;###autoload |
|
1187 |
(add-hook 'slime-connected-hook 'clojure-enable-slime-on-existing-buffers) |
|
1188 |
|
|
1189 |
(add-hook 'slime-indentation-update-hooks 'put-clojure-indent) |
|
1190 |
|
|
1191 |
|
|
1192 |
|
|
1193 |
;;;###autoload |
|
1194 |
(add-to-list 'auto-mode-alist '("\\.clj$" . clojure-mode)) |
|
1195 |
(add-to-list 'interpreter-mode-alist '("jark" . clojure-mode)) |
|
1196 |
(add-to-list 'interpreter-mode-alist '("cake" . clojure-mode)) |
|
1197 |
|
|
1198 |
(provide 'clojure-mode) |
|
1199 |
;;; clojure-mode.el ends here |