clojure用emacsの設定

slimeとか良く分からないので、必要そうなものを自分で書いた。
あとでここに書いてある設定が不要になることもあるかもしれない。

run-clojure

schemeの設定と同様に"C-c S"でclojureのreplを立ち上げる。
既にreplが立ち上がっている場合には、other-windowにreplを表示

  • "C-x C-e"でS式をreplに送る
  • "C-c C-e"で{beginnig,end}-of-defunの範囲をreplに送る
  • "C-c C-l"で現在のbufferの内容を全部replに送る

clojure-eval

渡されたS式をclojureのreplで実行する。その実行結果を文字列として返す。
これでreplと通信できるようになった。(片方向だけ)
これを使って以下のようなコマンドを作った。

  • clojure-find-doc(find-docへのinterface)
  • clojure-output/arrow(実行結果を"->"の後に表示)

clojure-insert-closing-paren

"{"でも"["でも"("でも閉じる時は")"で済ませたい。

{[()]} ;{[()))と入力

code

(require 'clojure-mode)
(eval-when-compile (require 'cl))

(defvar clojure-program-name "clojure")
(defvar clojure-repl-name "*clojure*")

(defun run-clojure (cmd) 
  (interactive (list (if current-prefix-arg
			 (read-string "Run Clojure: " clojure-program-name)
			 clojure-program-name)))
  (let ((buf clojure-repl-name))
    (unless  (comint-check-proc buf)
      (let ((cmdlist (split-string-and-unquote cmd)))
	(make-comint "clojure" cmd)
	(setq clojure-program-name cmd)
	(setq clojure-buffer buf)))
    (display-buffer buf)))

(defun clojure-kill-repl () (interactive)
  (let ((repl-buf (get-buffer clojure-repl-name)))
    (when repl-buf
      (kill-buffer repl-buf)
      (when (> (count-windows) 1) 
	(delete-other-windows)))))

(defun clojure-repl-alive-p (&optional forcep)
  (or (get-buffer clojure-repl-name)
      (and forcep (run-clojure clojure-program-name))))

(defun clojure-repl-wakeup ()
  (clojure-repl-alive-p t))

;;;connect with repl
;;internal variable
(defvar clojure-output-storage nil)
(defvar clojure-reading-p nil)
(defvar clojure-output-filter-functions  nil)
(defvar clojure-reading-timelimit 5)
(setq clojure-filtering-context-visible-p nil)
(defmacro clojure-filter-function-maker (regexp)
  (let (( s (gensym)))
    `(lambda (,s)
       (cond ((string-match-p ,regexp ,s)
	      (setq clojure-reading-p nil)
	      (push (replace-regexp-in-string ,regexp "" ,s)
		    clojure-output-storage))
	     (t (push ,s clojure-output-storage)))
       (if clojure-filtering-context-visible-p ,s ""))))

(setq clojure-output-filter-functions 
      (list (clojure-filter-function-maker "\n?.+=> $")))

(defun clojure-stop-reading () (interactive) 
  (setq clojure-reading-p nil))

(defsubst clojure-cleanup-storage ()
  (setq clojure-output-storage nil))

(defmacro %clojure-connect-repl (send-action)
  `(let ((clojure-reading-p t)
	 (old-comint-filter comint-preoutput-filter-functions)
	 (comint-preoutput-filter-functions clojure-output-filter-functions))
     (run-at-time clojure-reading-timelimit nil 'clojure-stop-reading) ;;for safe(time limit)
     (unwind-protect
	 (progn (clojure-cleanup-storage)
		,send-action
		(while clojure-reading-p (sleep-for 0 100))
		(apply 'concat (reverse clojure-output-storage)))
       (setq comint-preoutput-filter-functions old-comint-filter
	     clojure-reading-p nil)
       )))

(defmacro clojure-connect-with-repl (send-func)
  `(progn (clojure-repl-wakeup)			;prepare
	  (%clojure-connect-repl ,send-func)))

(defun clojure-eval (sexp)
  (let ((content (with-output-to-string (prin1 sexp))))
    (clojure-connect-with-repl
     (clojure-send-string content t))))

;;;find-doc interface
(defvar clojure-find-doc-history nil)
(defsubst clojure-read-arg ()
  (list 
   (read-from-minibuffer 
    "finddoc(M-n,M-p lookup history):" ""
    minibuffer-local-map nil 'clojure-find-doc-history)))

(defun clojure-find-doc (rx)  (interactive (clojure-read-arg))
  (add-to-list 'clojure-find-doc-history rx)
  (with-output-to-temp-buffer "*clojure-find-doc*"
    (princ (clojure-eval `(find-doc ,rx))))
  (clojure-search-forcus-other-window
   "*clojure-find-doc*" rx))

(defun clojure-find-doc-at-point () (interactive)
  (clojure-find-doc (current-word)))

(defun clojure-search-forcus-other-window (buf rx)
  (let ((w (some-window
	    (lambda (w) (string= buf (buffer-name (window-buffer w))))
	    'no-minibuffer 'current-frame)))
    (and w (with-selected-window w
	     (re-search-forward (format "^[^ ]+/%s" rx) nil t)
	     (recenter 0)
	     ;; (save-excursion
	     ;;   (let* ((beg (point))
	     ;; 	      (end (progn (re-search-forward "^-+$" nil t)
	     ;; 			  (match-beginning 0)))
	     ;; 	      (ov (make-overlay beg end)))
	     ;; 	 (overlay-put ov 'face 'region)))))))
	     ))))

;;eval via (C-x C-e .etc)
(defun clojure-send-string (str &optional newline-p)
  (comint-send-string
   (get-process "clojure")
   (if newline-p (format "%s\n" str) str)))

(defun clojure-eval-with-action (beg-ac end-ac &optional newline-p)
  (let* ((end (progn (funcall end-ac) (point)))
	 (beg (progn (funcall beg-ac) (point)))
	 (str (buffer-substring-no-properties beg end))
	 (newline-p* (or newline-p (= (point-max) end))))
    (clojure-send-string str newline-p*)))

(defun clojure-eval-last-sexp () (interactive)
  (clojure-eval-with-action 
   'beginning-of-sexp 'end-of-sexp t))

(defun clojure-eval-last-defun () (interactive)
  (save-excursion
    (clojure-eval-with-action
     'beginning-of-defun 'end-of-defun)))

(defun clojure-eval-buffer () (interactive)
  (save-excursion
    (clojure-eval-with-action 
     'beginning-of-buffer 'end-of-buffer t)))

(defun clojure-output/arrow () (interactive) ;行頭にカーソルが合ったときに上手くいかない。
  (let ((arrow " ; => ")
  	(r (clojure-connect-with-repl 
  	    (clojure-eval-last-defun))))
    (goto-char (point-at-bol))
    (when (re-search-forward arrow (point-at-eol) t)
      (delete-region (match-beginning 0) (point-at-eol)))
    (goto-char (point-at-eol))
    (insert arrow (replace-regexp-in-string "\n" "" r))))


;;; convenient function when insert closing(paren, brace...)
(defun clojure-insert-closing (prefix default-close others) 
  ;;others = ((open . close) ({ . }) ...)
  (insert default-close) 
  (unless prefix 
    (let ((open-pt (condition-case nil 
		       (scan-sexps (point) -1) 
		     (error (beep) nil)))) 
      (when open-pt 
	(let ((open-char (aref (buffer-substring-no-properties
				open-pt (1+ open-pt))
			       0))) 
	  (and-let* ((other-close (assoc-default open-char others)))
	    (delete-backward-char 1)
	    (insert other-close)))))))

(defun clojure-insert-closing-paren (&optional prefix) (interactive "P") 
  (clojure-insert-closing prefix ?\) '((?\[ . ?\]) (?\{ . ?\}))))


;;;setting
(setq my-clojure-key-bindings
      '(("\C-cS" . run-clojure)
	("\C-c\C-i" . clojure-output/arrow)
	("\C-c\C-k" . clojure-kill-repl)
	("\C-x\C-e" . clojure-eval-last-sexp)
	("\C-c\C-e" . clojure-eval-last-defun)
	("\C-c\C-l" . clojure-eval-buffer)
	(")" . clojure-insert-closing-paren)
	("\C-hd" . clojure-find-doc-at-point)
	("\C-hf" . clojure-find-doc)))

(add-hook 'clojure-mode-hook 
	  (lambda ()
	    (loop for (key . cmd) in my-clojure-key-bindings
		  do (define-key clojure-mode-map key cmd))))