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