diff .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
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/.elisp/clojure-mode.el
@@ -0,0 +1,488 @@
+;;; clojure-mode.el -- Major mode for Clojure code
+
+;; Copyright (C) 2008 Jeffrey Chu
+;;
+;; Author: Jeffrey Chu <jochu0@gmail.com>
+;; 
+;;   Originally by: Lennart Staflin <lenst@lysator.liu.se>
+;;                  Copyright (C) 2007, 2008 Lennart Staflin
+;;
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License
+;; as published by the Free Software Foundation; either version 3
+;; of the License, or (at your option) any later version.
+;; 
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;; 
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+(require 'cl)
+
+(defgroup clojure-mode nil
+  "A mode for Clojure"
+  :prefix "clojure-mode-"
+  :group 'applications)
+
+(defcustom clojure-mode-font-lock-multiline-def t
+  "Set to non-nil in order to enable font-lock of
+multi-line (def...) forms. Changing this will require a
+restart (ie. M-x clojure-mode) of existing clojure mode buffers."
+  :type 'boolean
+  :group 'clojure-mode)
+
+(defcustom clojure-mode-font-lock-comment-sexp nil
+  "Set to non-nil in order to enable font-lock of (comment...)
+forms. This option is experimental. Changing this will require a
+restart (ie. M-x clojure-mode) of existing clojure mode buffers."
+  :type 'boolean
+  :group 'clojure-mode)
+
+(defcustom clojure-mode-load-command  "(clojure/load-file \"%s\")\n"
+  "*Format-string for building a Clojure expression to load a file.
+This format string should use `%s' to substitute a file name
+and should result in a Clojure expression that will command the inferior Clojure
+to load that file."
+  :type 'string
+  :group 'clojure-mode)
+
+(defcustom clojure-mode-use-backtracking-indent nil
+  "Set to non-nil to enable backtracking/context sensitive
+indentation."
+  :type 'boolean
+  :group 'clojure-mode)
+
+(defcustom clojure-max-backtracking 3
+  "Maximum amount to backtrack up a list to check for context."
+  :type 'integer
+  :group 'clojure-mode)
+
+
+(defvar clojure-mode-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map lisp-mode-shared-map)
+    (define-key map "\e\C-x" 'lisp-eval-defun)
+    (define-key map "\C-x\C-e" 'lisp-eval-last-sexp)
+    (define-key map "\C-c\C-e" 'lisp-eval-last-sexp)
+    (define-key map "\C-c\C-l" 'clojure-load-file)
+    (define-key map "\C-c\C-r" 'lisp-eval-region)
+    (define-key map "\C-c\C-z" 'run-lisp)
+    map)
+  "Keymap for ordinary Clojure mode.
+All commands in `lisp-mode-shared-map' are inherited by this map.")
+
+
+(easy-menu-define clojure-menu clojure-mode-map "Menu used in `clojure-mode'."
+  '("Clojure"
+    ["Eval defun"         lisp-eval-defun         t]
+    ["Eval defun and go"  lisp-eval-defun-and-go  t]
+    ["Eval last sexp"     lisp-eval-last-sexp     t]
+    ["Eval region"        lisp-eval-region        t]
+    ["Eval region and go" lisp-eval-region-and-go t]
+    ["Load file..."       clojure-load-file       t]
+    ["Run Lisp"           run-lisp                t]))
+
+
+(defvar clojure-mode-syntax-table
+  (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table)))
+    (modify-syntax-entry ?~ "'   " table)
+    (modify-syntax-entry ?, "    " table)
+    (modify-syntax-entry ?\{ "(}" table)
+    (modify-syntax-entry ?\} "){" table)
+    (modify-syntax-entry ?\[ "(]" table)
+    (modify-syntax-entry ?\] ")[" table)
+    (modify-syntax-entry ?^ "'" table)
+    (modify-syntax-entry ?= "'" table)
+    table))
+
+
+(defvar clojure-prev-l/c-dir/file nil
+  "Record last directory and file used in loading or compiling.
+This holds a cons cell of the form `(DIRECTORY . FILE)'
+describing the last `clojure-load-file' or `clojure-compile-file' command.")
+
+
+(defun clojure-mode ()
+  "Major mode for editing Clojure code - similar to Lisp mode..
+Commands:
+Delete converts tabs to spaces as it moves back.
+Blank lines separate paragraphs.  Semicolons start comments.
+\\{clojure-mode-map}
+Note that `run-lisp' may be used either to start an inferior Lisp job
+or to switch back to an existing one.
+
+Entry to this mode calls the value of `clojure-mode-hook'
+if that value is non-nil."
+  (interactive)
+  (kill-all-local-variables)
+  (use-local-map clojure-mode-map)
+  (setq major-mode 'clojure-mode)
+  (setq mode-name "Clojure")
+  (lisp-mode-variables nil)
+  (set-syntax-table clojure-mode-syntax-table)
+  
+  (set (make-local-variable 'comment-start-skip)
+       "\\(\\(^\\|[^\\\\\n]\\)\\(\\\\\\\\\\)*\\)\\(;+\\|#|\\) *")
+  (set (make-local-variable 'lisp-indent-function)
+       'clojure-indent-function)
+  (set (make-local-variable 'font-lock-multiline) t)
+
+  (if (and (not (boundp 'font-lock-extend-region-functions))
+           (or clojure-mode-font-lock-multiline-def
+               clojure-mode-font-lock-comment-sexp))
+      (message "Clojure mode font lock extras are unavailable, please upgrade to atleast version 22 ")
+    
+   (when clojure-mode-font-lock-multiline-def
+     (add-to-list 'font-lock-extend-region-functions 'clojure-font-lock-extend-region-def t))
+   
+   (when clojure-mode-font-lock-comment-sexp
+     (add-to-list 'font-lock-extend-region-functions 'clojure-font-lock-extend-region-comment t)
+     (make-local-variable 'clojure-font-lock-keywords)
+     (add-to-list 'clojure-font-lock-keywords  'clojure-font-lock-mark-comment t)
+     (set (make-local-variable 'open-paren-in-column-0-is-defun-start) nil)))
+
+  (setq font-lock-defaults
+	'(clojure-font-lock-keywords    ; keywords
+	  nil nil
+          (("+-*/.<>=!?$%_&~^:@" . "w")) ; syntax alist
+          nil
+	  (font-lock-mark-block-function . mark-defun)
+	  (font-lock-syntactic-face-function . lisp-font-lock-syntactic-face-function)))
+  
+  (run-mode-hooks 'clojure-mode-hook))
+
+(defun clojure-font-lock-def-at-point (point)
+  "Find the position range between the top-most def* and the
+fourth element afterwards. Note that this means there's no
+gaurantee of proper font locking in def* forms that are not at
+top-level."
+  (goto-char point)
+  (condition-case nil
+      (beginning-of-defun)
+    (error nil))
+  
+  (let ((beg-def (point)))
+    (when (and (not (= point beg-def))
+               (looking-at "(def"))
+      (condition-case nil
+       (progn
+         ;; move forward as much as possible until failure (or success)
+         (forward-char)
+         (dotimes (i 4)
+           (forward-sexp)))
+       (error nil))
+      (cons beg-def (point)))))
+
+(defun clojure-font-lock-extend-region-def ()
+  "Move fontification boundaries to always include the first four
+elements of a def* forms."
+  (let ((changed nil))
+    (let ((def (clojure-font-lock-def-at-point font-lock-beg)))
+      (when def
+       (destructuring-bind (def-beg . def-end) def
+         (when (and (< def-beg font-lock-beg)
+                    (< font-lock-beg def-end))
+           (setq font-lock-beg def-beg
+                 changed t)))))
+
+    (let ((def (clojure-font-lock-def-at-point font-lock-end)))
+      (when def
+       (destructuring-bind (def-beg . def-end) def
+         (when (and (< def-beg font-lock-end)
+                    (< font-lock-end def-end))
+           (setq font-lock-end def-end
+                 changed t)))))
+    changed))
+
+(defun clojure-font-lock-extend-region-comment ()
+  "Move fontification boundaries to always contain
+  entire (comment ..) sexp. Does not work if you have a
+  white-space between ( and comment, but that is omitted to make
+  this run faster."
+  (let ((changed nil))
+    (goto-char font-lock-beg)
+    (condition-case nil (beginning-of-defun) (error nil))
+    (let ((pos (re-search-forward "(comment\\>" font-lock-end t)))
+      (when pos
+        (forward-char -8)
+        (when (< (point) font-lock-beg)
+          (setq font-lock-beg (point)
+                changed t))
+        (condition-case nil (forward-sexp) (error nil))
+        (when (> (point) font-lock-end)
+          (setq font-lock-end (point)
+                changed t))))
+    changed))
+        
+
+(defun clojure-font-lock-mark-comment (limit)
+  "Marks all (comment ..) forms with font-lock-comment-face."
+  (let (pos)
+    (while (and (< (point) limit)
+                (setq pos (re-search-forward "(comment\\>" limit t)))
+      (when pos
+        (forward-char -8)
+        (condition-case nil
+            (add-text-properties (1+ (point)) (progn (forward-sexp) (1- (point)))
+                                 '(face font-lock-comment-face multiline t))
+          (error (forward-char 8))))))
+  nil)
+
+(defconst clojure-font-lock-keywords
+  (eval-when-compile
+    `( ;; Definitions.
+      (,(concat "(\\(?:clojure/\\)?\\(def"
+		;; Function declarations.
+		"\\(n-?\\|multi\\|macro\\|method\\|"
+		;; Variable declarations.
+                "struct\\|once\\|"
+		"\\)\\)\\>"
+		;; Any whitespace
+		"[ \r\n\t]*"
+                ;; Possibly type or metadata
+                "\\(?:#^\\(?:{[^}]*}\\|\\sw+\\)[ \r\n\t]*\\)?"
+                
+                "\\(\\sw+\\)?")
+        (1 font-lock-keyword-face)
+        (3 font-lock-function-name-face nil t))
+      ;; Control structures
+      (,(concat
+         "(\\(?:clojure/\\)?" 
+         (regexp-opt
+          '("cond" "for" "loop" "let" "recur" "do" "binding" "with-meta"
+            "when" "when-not" "when-let" "when-first" "if" "if-let"
+            "delay" "lazy-cons" "." ".." "->" "and" "or" "locking"
+            "dosync" "load"
+            "sync" "doseq" "dotimes" "import" "unimport" "ns" "in-ns" "refer"
+            "implement" "proxy" "time" "try" "catch" "finally" "throw"
+            "doto" "with-open" "with-local-vars" "struct-map"
+            "gen-class" "gen-and-load-class" "gen-and-save-class") t)
+         "\\>")
+        .  1)
+      ;; (fn name? args ...)
+      (,(concat "(\\(?:clojure/\\)?\\(fn\\)[ \t]+"
+                ;; Possibly type
+                "\\(?:#^\\sw+[ \t]*\\)?"
+                ;; Possibly name
+                "\\(\\sw+\\)?" )
+        (1 font-lock-keyword-face)
+        (2 font-lock-function-name-face nil t))
+      ;; Constant values.
+      ("\\<:\\sw+\\>" 0 font-lock-builtin-face)
+      ;; Meta type annotation #^Type
+      ("#^\\sw+" 0 font-lock-type-face)
+      ))
+  "Default expressions to highlight in Clojure mode.")
+
+
+(defun clojure-load-file (file-name)
+  "Load a Lisp file into the inferior Lisp process."
+  (interactive (comint-get-source "Load Clojure file: " clojure-prev-l/c-dir/file
+				  '(clojure-mode) t))
+  (comint-check-source file-name) ; Check to see if buffer needs saved.
+  (setq clojure-prev-l/c-dir/file (cons (file-name-directory file-name)
+				     (file-name-nondirectory file-name)))
+  (comint-send-string (inferior-lisp-proc)
+		      (format clojure-mode-load-command file-name))
+  (switch-to-lisp t))
+
+
+
+(defun clojure-indent-function (indent-point state)
+  "This function is the normal value of the variable `lisp-indent-function'.
+It is used when indenting a line within a function call, to see if the
+called function says anything special about how to indent the line.
+
+INDENT-POINT is the position where the user typed TAB, or equivalent.
+Point is located at the point to indent under (for default indentation);
+STATE is the `parse-partial-sexp' state for that position.
+
+If the current line is in a call to a Lisp function
+which has a non-nil property `lisp-indent-function',
+that specifies how to do the indentation.  The property value can be
+* `defun', meaning indent `defun'-style;
+* an integer N, meaning indent the first N arguments specially
+  like ordinary function arguments and then indent any further
+  arguments like a body;
+* a function to call just as this function was called.
+  If that function returns nil, that means it doesn't specify
+  the indentation.
+
+This function also returns nil meaning don't specify the indentation."
+  (let ((normal-indent (current-column)))
+    (goto-char (1+ (elt state 1)))
+    (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
+    (if (and (elt state 2)
+             (not (looking-at "\\sw\\|\\s_")))
+        ;; car of form doesn't seem to be a symbol
+        (progn
+          (if (not (> (save-excursion (forward-line 1) (point))
+                      calculate-lisp-indent-last-sexp))
+		(progn (goto-char calculate-lisp-indent-last-sexp)
+		       (beginning-of-line)
+		       (parse-partial-sexp (point)
+					   calculate-lisp-indent-last-sexp 0 t)))
+	    ;; Indent under the list or under the first sexp on the same
+	    ;; line as calculate-lisp-indent-last-sexp.  Note that first
+	    ;; thing on that line has to be complete sexp since we are
+          ;; inside the innermost containing sexp.
+          (backward-prefix-chars)
+          (if (and (eq (char-after (point)) ?\[)
+                   (eq (char-after (elt state 1)) ?\())
+              (+ (current-column) 2) ;; this is probably inside a defn
+            (current-column)))
+      (let ((function (buffer-substring (point)
+					(progn (forward-sexp 1) (point))))
+            (open-paren (elt state 1))
+	    method)
+	(setq method (get (intern-soft function) 'clojure-indent-function))
+        
+	(cond ((member (char-after open-paren) '(?\[ ?\{))
+	       (goto-char open-paren)
+               (1+ (current-column)))
+	      ((or (eq method 'defun)
+		   (and (null method)
+			(> (length function) 3)
+			(string-match "\\`\\(?:clojure/\\)?def" function)))
+	       (lisp-indent-defform state indent-point))
+              
+	      ((integerp method)
+	       (lisp-indent-specform method state
+				     indent-point normal-indent))
+	      (method
+		(funcall method indent-point state))
+              (clojure-mode-use-backtracking-indent
+               (clojure-backtracking-indent indent-point state normal-indent)))))))
+
+(defun clojure-backtracking-indent (indent-point state normal-indent)
+  "Experimental backtracking support. Will upwards in an sexp to
+check for contextual indenting."
+  (let (indent (path) (depth 0))
+    (goto-char (elt state 1))
+    (while (and (not indent)
+                (< depth clojure-max-backtracking))
+      (let ((containing-sexp (point)))
+        (parse-partial-sexp (1+ containing-sexp) indent-point 1 t)
+        (when (looking-at "\\sw\\|\\s_")
+          (let* ((start (point))
+                 (fn (buffer-substring start (progn (forward-sexp 1) (point))))
+                 (meth (get (intern-soft fn) 'clojure-backtracking-indent)))
+            (let ((n 0))
+              (when (< (point) indent-point)
+                (condition-case ()
+                    (progn
+                     (forward-sexp 1)
+                     (while (< (point) indent-point)
+                       (parse-partial-sexp (point) indent-point 1 t)
+                       (incf n)
+                       (forward-sexp 1)))
+                  (error nil)))
+              (push n path))
+            (when meth
+              (let ((def meth))
+                (dolist (p path)
+                  (if (and (listp def)
+                           (< p (length def)))
+                      (setq def (nth p def))
+                    (if (listp def)
+                        (setq def (car (last def)))
+                      (setq def nil))))
+                (goto-char (elt state 1))
+                (when def
+                  (setq indent (+ (current-column) def)))))))
+        (goto-char containing-sexp)
+        (condition-case ()
+            (progn
+              (backward-up-list 1)
+              (incf depth))
+          (error (setq depth clojure-max-backtracking)))))
+    indent))
+
+;; (defun clojure-indent-defn (indent-point state)
+;;   "Indent by 2 if after a [] clause that's at the beginning of a
+;; line"
+;;   (if (not (eq (char-after (elt state 2)) ?\[))
+;;       (lisp-indent-defform state indent-point)
+;;     (goto-char (elt state 2))
+;;     (beginning-of-line)
+;;     (skip-syntax-forward " ")
+;;     (if (= (point) (elt state 2))
+;;         (+ (current-column) 2)
+;;       (lisp-indent-defform state indent-point))))
+
+;; (put 'defn 'clojure-indent-function 'clojure-indent-defn)
+;; (put 'defmacro 'clojure-indent-function 'clojure-indent-defn)
+
+;; clojure backtracking indent is experimental and the format for these
+
+;; entries are subject to change
+(put 'implement 'clojure-backtracking-indent '(4 (2)))
+(put 'proxy 'clojure-backtracking-indent '(4 4 (2)))
+
+
+(defun put-clojure-indent (sym indent)
+  (put sym 'clojure-indent-function indent)
+  (put (intern (format "clojure/%s" (symbol-name sym))) 'clojure-indent-function indent))
+
+(defmacro define-clojure-indent (&rest kvs)
+  `(progn
+     ,@(mapcar (lambda (x) `(put-clojure-indent (quote ,(first x)) ,(second x))) kvs)))
+
+(define-clojure-indent
+  (catch 2)
+  (defmuti 1)
+  (do 0)
+  (for 1)    ; FIXME (for seqs expr) and (for seqs filter expr)
+  (if 1)
+  (let 1)
+  (loop 1)
+  (struct-map 1)
+  (assoc 1)
+
+  (fn 'defun))
+
+;; built-ins
+(define-clojure-indent
+  (ns 1)
+  (binding 1)
+  (comment 0)
+  (defstruct 1)
+  (doseq 1)
+  (dotimes 1)
+  (doto 1)
+  (implement 1)
+  (let 1)
+  (when-let 1)
+  (if-let 1)
+  (locking 1)
+  (proxy 2)
+  (sync 1)
+  (when 1)
+  (when-first 1)
+  (when-let 1)
+  (when-not 1)
+  (with-local-vars 1)
+  (with-open 1)
+  (with-precision 1))
+
+;; macro indent (auto generated)
+
+;; Things that just aren't right (manually removed)
+; (put '-> 'clojure-indent-function 2)
+; (put '.. 'clojure-indent-function 2)
+; (put 'and 'clojure-indent-function 1)
+; (put 'defmethod 'clojure-indent-function 2)
+; (put 'defn- 'clojure-indent-function 1)
+; (put 'memfn 'clojure-indent-function 1)
+; (put 'or 'clojure-indent-function 1)
+; (put 'lazy-cat 'clojure-indent-function 1)
+; (put 'lazy-cons 'clojure-indent-function 1)
+
+(provide 'clojure-mode)
+
+;;; clojure-mode.el ends here