Mercurial > dotfiles
comparison .elisp/clojure-mode.el @ 7:9541f7e47514
Some edits to .emacs after my playing with Clojure, also added Clojure support files.
author | Augie Fackler <durin42@gmail.com> |
---|---|
date | Sun, 30 Nov 2008 20:50:18 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
6:66f8fcaee427 | 7:9541f7e47514 |
---|---|
1 ;;; clojure-mode.el -- Major mode for Clojure code | |
2 | |
3 ;; Copyright (C) 2008 Jeffrey Chu | |
4 ;; | |
5 ;; Author: Jeffrey Chu <jochu0@gmail.com> | |
6 ;; | |
7 ;; Originally by: Lennart Staflin <lenst@lysator.liu.se> | |
8 ;; Copyright (C) 2007, 2008 Lennart Staflin | |
9 ;; | |
10 ;; This program is free software; you can redistribute it and/or | |
11 ;; modify it under the terms of the GNU General Public License | |
12 ;; as published by the Free Software Foundation; either version 3 | |
13 ;; of the License, or (at your option) any later version. | |
14 ;; | |
15 ;; This program is distributed in the hope that it will be useful, | |
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 ;; GNU General Public License for more details. | |
19 ;; | |
20 ;; You should have received a copy of the GNU General Public License | |
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the | |
22 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
23 ;; Boston, MA 02110-1301, USA. | |
24 | |
25 (require 'cl) | |
26 | |
27 (defgroup clojure-mode nil | |
28 "A mode for Clojure" | |
29 :prefix "clojure-mode-" | |
30 :group 'applications) | |
31 | |
32 (defcustom clojure-mode-font-lock-multiline-def t | |
33 "Set to non-nil in order to enable font-lock of | |
34 multi-line (def...) forms. Changing this will require a | |
35 restart (ie. M-x clojure-mode) of existing clojure mode buffers." | |
36 :type 'boolean | |
37 :group 'clojure-mode) | |
38 | |
39 (defcustom clojure-mode-font-lock-comment-sexp nil | |
40 "Set to non-nil in order to enable font-lock of (comment...) | |
41 forms. This option is experimental. Changing this will require a | |
42 restart (ie. M-x clojure-mode) of existing clojure mode buffers." | |
43 :type 'boolean | |
44 :group 'clojure-mode) | |
45 | |
46 (defcustom clojure-mode-load-command "(clojure/load-file \"%s\")\n" | |
47 "*Format-string for building a Clojure expression to load a file. | |
48 This format string should use `%s' to substitute a file name | |
49 and should result in a Clojure expression that will command the inferior Clojure | |
50 to load that file." | |
51 :type 'string | |
52 :group 'clojure-mode) | |
53 | |
54 (defcustom clojure-mode-use-backtracking-indent nil | |
55 "Set to non-nil to enable backtracking/context sensitive | |
56 indentation." | |
57 :type 'boolean | |
58 :group 'clojure-mode) | |
59 | |
60 (defcustom clojure-max-backtracking 3 | |
61 "Maximum amount to backtrack up a list to check for context." | |
62 :type 'integer | |
63 :group 'clojure-mode) | |
64 | |
65 | |
66 (defvar clojure-mode-map | |
67 (let ((map (make-sparse-keymap))) | |
68 (set-keymap-parent map lisp-mode-shared-map) | |
69 (define-key map "\e\C-x" 'lisp-eval-defun) | |
70 (define-key map "\C-x\C-e" 'lisp-eval-last-sexp) | |
71 (define-key map "\C-c\C-e" 'lisp-eval-last-sexp) | |
72 (define-key map "\C-c\C-l" 'clojure-load-file) | |
73 (define-key map "\C-c\C-r" 'lisp-eval-region) | |
74 (define-key map "\C-c\C-z" 'run-lisp) | |
75 map) | |
76 "Keymap for ordinary Clojure mode. | |
77 All commands in `lisp-mode-shared-map' are inherited by this map.") | |
78 | |
79 | |
80 (easy-menu-define clojure-menu clojure-mode-map "Menu used in `clojure-mode'." | |
81 '("Clojure" | |
82 ["Eval defun" lisp-eval-defun t] | |
83 ["Eval defun and go" lisp-eval-defun-and-go t] | |
84 ["Eval last sexp" lisp-eval-last-sexp t] | |
85 ["Eval region" lisp-eval-region t] | |
86 ["Eval region and go" lisp-eval-region-and-go t] | |
87 ["Load file..." clojure-load-file t] | |
88 ["Run Lisp" run-lisp t])) | |
89 | |
90 | |
91 (defvar clojure-mode-syntax-table | |
92 (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table))) | |
93 (modify-syntax-entry ?~ "' " table) | |
94 (modify-syntax-entry ?, " " table) | |
95 (modify-syntax-entry ?\{ "(}" table) | |
96 (modify-syntax-entry ?\} "){" table) | |
97 (modify-syntax-entry ?\[ "(]" table) | |
98 (modify-syntax-entry ?\] ")[" table) | |
99 (modify-syntax-entry ?^ "'" table) | |
100 (modify-syntax-entry ?= "'" table) | |
101 table)) | |
102 | |
103 | |
104 (defvar clojure-prev-l/c-dir/file nil | |
105 "Record last directory and file used in loading or compiling. | |
106 This holds a cons cell of the form `(DIRECTORY . FILE)' | |
107 describing the last `clojure-load-file' or `clojure-compile-file' command.") | |
108 | |
109 | |
110 (defun clojure-mode () | |
111 "Major mode for editing Clojure code - similar to Lisp mode.. | |
112 Commands: | |
113 Delete converts tabs to spaces as it moves back. | |
114 Blank lines separate paragraphs. Semicolons start comments. | |
115 \\{clojure-mode-map} | |
116 Note that `run-lisp' may be used either to start an inferior Lisp job | |
117 or to switch back to an existing one. | |
118 | |
119 Entry to this mode calls the value of `clojure-mode-hook' | |
120 if that value is non-nil." | |
121 (interactive) | |
122 (kill-all-local-variables) | |
123 (use-local-map clojure-mode-map) | |
124 (setq major-mode 'clojure-mode) | |
125 (setq mode-name "Clojure") | |
126 (lisp-mode-variables nil) | |
127 (set-syntax-table clojure-mode-syntax-table) | |
128 | |
129 (set (make-local-variable 'comment-start-skip) | |
130 "\\(\\(^\\|[^\\\\\n]\\)\\(\\\\\\\\\\)*\\)\\(;+\\|#|\\) *") | |
131 (set (make-local-variable 'lisp-indent-function) | |
132 'clojure-indent-function) | |
133 (set (make-local-variable 'font-lock-multiline) t) | |
134 | |
135 (if (and (not (boundp 'font-lock-extend-region-functions)) | |
136 (or clojure-mode-font-lock-multiline-def | |
137 clojure-mode-font-lock-comment-sexp)) | |
138 (message "Clojure mode font lock extras are unavailable, please upgrade to atleast version 22 ") | |
139 | |
140 (when clojure-mode-font-lock-multiline-def | |
141 (add-to-list 'font-lock-extend-region-functions 'clojure-font-lock-extend-region-def t)) | |
142 | |
143 (when clojure-mode-font-lock-comment-sexp | |
144 (add-to-list 'font-lock-extend-region-functions 'clojure-font-lock-extend-region-comment t) | |
145 (make-local-variable 'clojure-font-lock-keywords) | |
146 (add-to-list 'clojure-font-lock-keywords 'clojure-font-lock-mark-comment t) | |
147 (set (make-local-variable 'open-paren-in-column-0-is-defun-start) nil))) | |
148 | |
149 (setq font-lock-defaults | |
150 '(clojure-font-lock-keywords ; keywords | |
151 nil nil | |
152 (("+-*/.<>=!?$%_&~^:@" . "w")) ; syntax alist | |
153 nil | |
154 (font-lock-mark-block-function . mark-defun) | |
155 (font-lock-syntactic-face-function . lisp-font-lock-syntactic-face-function))) | |
156 | |
157 (run-mode-hooks 'clojure-mode-hook)) | |
158 | |
159 (defun clojure-font-lock-def-at-point (point) | |
160 "Find the position range between the top-most def* and the | |
161 fourth element afterwards. Note that this means there's no | |
162 gaurantee of proper font locking in def* forms that are not at | |
163 top-level." | |
164 (goto-char point) | |
165 (condition-case nil | |
166 (beginning-of-defun) | |
167 (error nil)) | |
168 | |
169 (let ((beg-def (point))) | |
170 (when (and (not (= point beg-def)) | |
171 (looking-at "(def")) | |
172 (condition-case nil | |
173 (progn | |
174 ;; move forward as much as possible until failure (or success) | |
175 (forward-char) | |
176 (dotimes (i 4) | |
177 (forward-sexp))) | |
178 (error nil)) | |
179 (cons beg-def (point))))) | |
180 | |
181 (defun clojure-font-lock-extend-region-def () | |
182 "Move fontification boundaries to always include the first four | |
183 elements of a def* forms." | |
184 (let ((changed nil)) | |
185 (let ((def (clojure-font-lock-def-at-point font-lock-beg))) | |
186 (when def | |
187 (destructuring-bind (def-beg . def-end) def | |
188 (when (and (< def-beg font-lock-beg) | |
189 (< font-lock-beg def-end)) | |
190 (setq font-lock-beg def-beg | |
191 changed t))))) | |
192 | |
193 (let ((def (clojure-font-lock-def-at-point font-lock-end))) | |
194 (when def | |
195 (destructuring-bind (def-beg . def-end) def | |
196 (when (and (< def-beg font-lock-end) | |
197 (< font-lock-end def-end)) | |
198 (setq font-lock-end def-end | |
199 changed t))))) | |
200 changed)) | |
201 | |
202 (defun clojure-font-lock-extend-region-comment () | |
203 "Move fontification boundaries to always contain | |
204 entire (comment ..) sexp. Does not work if you have a | |
205 white-space between ( and comment, but that is omitted to make | |
206 this run faster." | |
207 (let ((changed nil)) | |
208 (goto-char font-lock-beg) | |
209 (condition-case nil (beginning-of-defun) (error nil)) | |
210 (let ((pos (re-search-forward "(comment\\>" font-lock-end t))) | |
211 (when pos | |
212 (forward-char -8) | |
213 (when (< (point) font-lock-beg) | |
214 (setq font-lock-beg (point) | |
215 changed t)) | |
216 (condition-case nil (forward-sexp) (error nil)) | |
217 (when (> (point) font-lock-end) | |
218 (setq font-lock-end (point) | |
219 changed t)))) | |
220 changed)) | |
221 | |
222 | |
223 (defun clojure-font-lock-mark-comment (limit) | |
224 "Marks all (comment ..) forms with font-lock-comment-face." | |
225 (let (pos) | |
226 (while (and (< (point) limit) | |
227 (setq pos (re-search-forward "(comment\\>" limit t))) | |
228 (when pos | |
229 (forward-char -8) | |
230 (condition-case nil | |
231 (add-text-properties (1+ (point)) (progn (forward-sexp) (1- (point))) | |
232 '(face font-lock-comment-face multiline t)) | |
233 (error (forward-char 8)))))) | |
234 nil) | |
235 | |
236 (defconst clojure-font-lock-keywords | |
237 (eval-when-compile | |
238 `( ;; Definitions. | |
239 (,(concat "(\\(?:clojure/\\)?\\(def" | |
240 ;; Function declarations. | |
241 "\\(n-?\\|multi\\|macro\\|method\\|" | |
242 ;; Variable declarations. | |
243 "struct\\|once\\|" | |
244 "\\)\\)\\>" | |
245 ;; Any whitespace | |
246 "[ \r\n\t]*" | |
247 ;; Possibly type or metadata | |
248 "\\(?:#^\\(?:{[^}]*}\\|\\sw+\\)[ \r\n\t]*\\)?" | |
249 | |
250 "\\(\\sw+\\)?") | |
251 (1 font-lock-keyword-face) | |
252 (3 font-lock-function-name-face nil t)) | |
253 ;; Control structures | |
254 (,(concat | |
255 "(\\(?:clojure/\\)?" | |
256 (regexp-opt | |
257 '("cond" "for" "loop" "let" "recur" "do" "binding" "with-meta" | |
258 "when" "when-not" "when-let" "when-first" "if" "if-let" | |
259 "delay" "lazy-cons" "." ".." "->" "and" "or" "locking" | |
260 "dosync" "load" | |
261 "sync" "doseq" "dotimes" "import" "unimport" "ns" "in-ns" "refer" | |
262 "implement" "proxy" "time" "try" "catch" "finally" "throw" | |
263 "doto" "with-open" "with-local-vars" "struct-map" | |
264 "gen-class" "gen-and-load-class" "gen-and-save-class") t) | |
265 "\\>") | |
266 . 1) | |
267 ;; (fn name? args ...) | |
268 (,(concat "(\\(?:clojure/\\)?\\(fn\\)[ \t]+" | |
269 ;; Possibly type | |
270 "\\(?:#^\\sw+[ \t]*\\)?" | |
271 ;; Possibly name | |
272 "\\(\\sw+\\)?" ) | |
273 (1 font-lock-keyword-face) | |
274 (2 font-lock-function-name-face nil t)) | |
275 ;; Constant values. | |
276 ("\\<:\\sw+\\>" 0 font-lock-builtin-face) | |
277 ;; Meta type annotation #^Type | |
278 ("#^\\sw+" 0 font-lock-type-face) | |
279 )) | |
280 "Default expressions to highlight in Clojure mode.") | |
281 | |
282 | |
283 (defun clojure-load-file (file-name) | |
284 "Load a Lisp file into the inferior Lisp process." | |
285 (interactive (comint-get-source "Load Clojure file: " clojure-prev-l/c-dir/file | |
286 '(clojure-mode) t)) | |
287 (comint-check-source file-name) ; Check to see if buffer needs saved. | |
288 (setq clojure-prev-l/c-dir/file (cons (file-name-directory file-name) | |
289 (file-name-nondirectory file-name))) | |
290 (comint-send-string (inferior-lisp-proc) | |
291 (format clojure-mode-load-command file-name)) | |
292 (switch-to-lisp t)) | |
293 | |
294 | |
295 | |
296 (defun clojure-indent-function (indent-point state) | |
297 "This function is the normal value of the variable `lisp-indent-function'. | |
298 It is used when indenting a line within a function call, to see if the | |
299 called function says anything special about how to indent the line. | |
300 | |
301 INDENT-POINT is the position where the user typed TAB, or equivalent. | |
302 Point is located at the point to indent under (for default indentation); | |
303 STATE is the `parse-partial-sexp' state for that position. | |
304 | |
305 If the current line is in a call to a Lisp function | |
306 which has a non-nil property `lisp-indent-function', | |
307 that specifies how to do the indentation. The property value can be | |
308 * `defun', meaning indent `defun'-style; | |
309 * an integer N, meaning indent the first N arguments specially | |
310 like ordinary function arguments and then indent any further | |
311 arguments like a body; | |
312 * a function to call just as this function was called. | |
313 If that function returns nil, that means it doesn't specify | |
314 the indentation. | |
315 | |
316 This function also returns nil meaning don't specify the indentation." | |
317 (let ((normal-indent (current-column))) | |
318 (goto-char (1+ (elt state 1))) | |
319 (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t) | |
320 (if (and (elt state 2) | |
321 (not (looking-at "\\sw\\|\\s_"))) | |
322 ;; car of form doesn't seem to be a symbol | |
323 (progn | |
324 (if (not (> (save-excursion (forward-line 1) (point)) | |
325 calculate-lisp-indent-last-sexp)) | |
326 (progn (goto-char calculate-lisp-indent-last-sexp) | |
327 (beginning-of-line) | |
328 (parse-partial-sexp (point) | |
329 calculate-lisp-indent-last-sexp 0 t))) | |
330 ;; Indent under the list or under the first sexp on the same | |
331 ;; line as calculate-lisp-indent-last-sexp. Note that first | |
332 ;; thing on that line has to be complete sexp since we are | |
333 ;; inside the innermost containing sexp. | |
334 (backward-prefix-chars) | |
335 (if (and (eq (char-after (point)) ?\[) | |
336 (eq (char-after (elt state 1)) ?\()) | |
337 (+ (current-column) 2) ;; this is probably inside a defn | |
338 (current-column))) | |
339 (let ((function (buffer-substring (point) | |
340 (progn (forward-sexp 1) (point)))) | |
341 (open-paren (elt state 1)) | |
342 method) | |
343 (setq method (get (intern-soft function) 'clojure-indent-function)) | |
344 | |
345 (cond ((member (char-after open-paren) '(?\[ ?\{)) | |
346 (goto-char open-paren) | |
347 (1+ (current-column))) | |
348 ((or (eq method 'defun) | |
349 (and (null method) | |
350 (> (length function) 3) | |
351 (string-match "\\`\\(?:clojure/\\)?def" function))) | |
352 (lisp-indent-defform state indent-point)) | |
353 | |
354 ((integerp method) | |
355 (lisp-indent-specform method state | |
356 indent-point normal-indent)) | |
357 (method | |
358 (funcall method indent-point state)) | |
359 (clojure-mode-use-backtracking-indent | |
360 (clojure-backtracking-indent indent-point state normal-indent))))))) | |
361 | |
362 (defun clojure-backtracking-indent (indent-point state normal-indent) | |
363 "Experimental backtracking support. Will upwards in an sexp to | |
364 check for contextual indenting." | |
365 (let (indent (path) (depth 0)) | |
366 (goto-char (elt state 1)) | |
367 (while (and (not indent) | |
368 (< depth clojure-max-backtracking)) | |
369 (let ((containing-sexp (point))) | |
370 (parse-partial-sexp (1+ containing-sexp) indent-point 1 t) | |
371 (when (looking-at "\\sw\\|\\s_") | |
372 (let* ((start (point)) | |
373 (fn (buffer-substring start (progn (forward-sexp 1) (point)))) | |
374 (meth (get (intern-soft fn) 'clojure-backtracking-indent))) | |
375 (let ((n 0)) | |
376 (when (< (point) indent-point) | |
377 (condition-case () | |
378 (progn | |
379 (forward-sexp 1) | |
380 (while (< (point) indent-point) | |
381 (parse-partial-sexp (point) indent-point 1 t) | |
382 (incf n) | |
383 (forward-sexp 1))) | |
384 (error nil))) | |
385 (push n path)) | |
386 (when meth | |
387 (let ((def meth)) | |
388 (dolist (p path) | |
389 (if (and (listp def) | |
390 (< p (length def))) | |
391 (setq def (nth p def)) | |
392 (if (listp def) | |
393 (setq def (car (last def))) | |
394 (setq def nil)))) | |
395 (goto-char (elt state 1)) | |
396 (when def | |
397 (setq indent (+ (current-column) def))))))) | |
398 (goto-char containing-sexp) | |
399 (condition-case () | |
400 (progn | |
401 (backward-up-list 1) | |
402 (incf depth)) | |
403 (error (setq depth clojure-max-backtracking))))) | |
404 indent)) | |
405 | |
406 ;; (defun clojure-indent-defn (indent-point state) | |
407 ;; "Indent by 2 if after a [] clause that's at the beginning of a | |
408 ;; line" | |
409 ;; (if (not (eq (char-after (elt state 2)) ?\[)) | |
410 ;; (lisp-indent-defform state indent-point) | |
411 ;; (goto-char (elt state 2)) | |
412 ;; (beginning-of-line) | |
413 ;; (skip-syntax-forward " ") | |
414 ;; (if (= (point) (elt state 2)) | |
415 ;; (+ (current-column) 2) | |
416 ;; (lisp-indent-defform state indent-point)))) | |
417 | |
418 ;; (put 'defn 'clojure-indent-function 'clojure-indent-defn) | |
419 ;; (put 'defmacro 'clojure-indent-function 'clojure-indent-defn) | |
420 | |
421 ;; clojure backtracking indent is experimental and the format for these | |
422 | |
423 ;; entries are subject to change | |
424 (put 'implement 'clojure-backtracking-indent '(4 (2))) | |
425 (put 'proxy 'clojure-backtracking-indent '(4 4 (2))) | |
426 | |
427 | |
428 (defun put-clojure-indent (sym indent) | |
429 (put sym 'clojure-indent-function indent) | |
430 (put (intern (format "clojure/%s" (symbol-name sym))) 'clojure-indent-function indent)) | |
431 | |
432 (defmacro define-clojure-indent (&rest kvs) | |
433 `(progn | |
434 ,@(mapcar (lambda (x) `(put-clojure-indent (quote ,(first x)) ,(second x))) kvs))) | |
435 | |
436 (define-clojure-indent | |
437 (catch 2) | |
438 (defmuti 1) | |
439 (do 0) | |
440 (for 1) ; FIXME (for seqs expr) and (for seqs filter expr) | |
441 (if 1) | |
442 (let 1) | |
443 (loop 1) | |
444 (struct-map 1) | |
445 (assoc 1) | |
446 | |
447 (fn 'defun)) | |
448 | |
449 ;; built-ins | |
450 (define-clojure-indent | |
451 (ns 1) | |
452 (binding 1) | |
453 (comment 0) | |
454 (defstruct 1) | |
455 (doseq 1) | |
456 (dotimes 1) | |
457 (doto 1) | |
458 (implement 1) | |
459 (let 1) | |
460 (when-let 1) | |
461 (if-let 1) | |
462 (locking 1) | |
463 (proxy 2) | |
464 (sync 1) | |
465 (when 1) | |
466 (when-first 1) | |
467 (when-let 1) | |
468 (when-not 1) | |
469 (with-local-vars 1) | |
470 (with-open 1) | |
471 (with-precision 1)) | |
472 | |
473 ;; macro indent (auto generated) | |
474 | |
475 ;; Things that just aren't right (manually removed) | |
476 ; (put '-> 'clojure-indent-function 2) | |
477 ; (put '.. 'clojure-indent-function 2) | |
478 ; (put 'and 'clojure-indent-function 1) | |
479 ; (put 'defmethod 'clojure-indent-function 2) | |
480 ; (put 'defn- 'clojure-indent-function 1) | |
481 ; (put 'memfn 'clojure-indent-function 1) | |
482 ; (put 'or 'clojure-indent-function 1) | |
483 ; (put 'lazy-cat 'clojure-indent-function 1) | |
484 ; (put 'lazy-cons 'clojure-indent-function 1) | |
485 | |
486 (provide 'clojure-mode) | |
487 | |
488 ;;; clojure-mode.el ends here |