249 lines
10 KiB
EmacsLisp
249 lines
10 KiB
EmacsLisp
;;; cider.el --- Clojure Integrated Development Environment and REPL -*- lexical-binding: t -*-
|
|
|
|
;; Copyright © 2012-2014 Tim King, Phil Hagelberg
|
|
;; Copyright © 2013-2014 Bozhidar Batsov, Hugo Duncan, Steve Purcell
|
|
;;
|
|
;; Author: Tim King <kingtim@gmail.com>
|
|
;; Phil Hagelberg <technomancy@gmail.com>
|
|
;; Bozhidar Batsov <bozhidar@batsov.com>
|
|
;; Hugo Duncan <hugo@hugoduncan.org>
|
|
;; Steve Purcell <steve@sanityinc.com>
|
|
;; URL: http://www.github.com/clojure-emacs/cider
|
|
;; Version: 0.8.1
|
|
;; Package-Requires: ((clojure-mode "3.0.0") (cl-lib "0.5") (dash "2.4.1") (pkg-info "0.4") (emacs "24") (queue "0.1.1"))
|
|
;; Keywords: languages, clojure, cider
|
|
|
|
;; 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
;; This file is not part of GNU Emacs.
|
|
|
|
;;; Commentary:
|
|
|
|
;; Provides a Clojure IDE and REPL for Emacs, built on top of nREPL.
|
|
|
|
;;; Installation:
|
|
|
|
;; Available as a package in marmalade-repo.org and melpa.milkbox.net.
|
|
|
|
;; (add-to-list 'package-archives
|
|
;; '("marmalade" . "http://marmalade-repo.org/packages/"))
|
|
;;
|
|
;; or
|
|
;;
|
|
;; (add-to-list 'package-archives
|
|
;; '("melpa" . "http://melpa.milkbox.net/packages/") t)
|
|
;;
|
|
;; M-x package-install cider
|
|
|
|
;;; Usage:
|
|
|
|
;; M-x cider-jack-in
|
|
|
|
;;; Code:
|
|
|
|
(defgroup cider nil
|
|
"Clojure Integrated Development Environment and REPL."
|
|
:prefix "cider-"
|
|
:group 'applications
|
|
:link '(url-link :tag "Github" "https://github.com/clojure-emacs/cider")
|
|
:link '(emacs-commentary-link :tag "Commentary" "cider"))
|
|
|
|
(require 'cider-client)
|
|
(require 'cider-interaction)
|
|
(require 'cider-eldoc)
|
|
(require 'cider-repl)
|
|
(require 'cider-mode)
|
|
(require 'cider-util)
|
|
(require 'tramp-sh)
|
|
|
|
(defvar cider-version "0.8.1"
|
|
"Fallback version used when it cannot be extracted automatically.
|
|
Normally it won't be used, unless `pkg-info' fails to extract the
|
|
version from the CIDER package or library.")
|
|
|
|
(defcustom cider-lein-command
|
|
"lein"
|
|
"The command used to execute Leiningen 2.x."
|
|
:type 'string
|
|
:group 'cider)
|
|
|
|
(defcustom cider-lein-parameters
|
|
"repl :headless"
|
|
"Params passed to lein to start an nREPL server via `cider-jack-in'."
|
|
:type 'string
|
|
:group 'cider)
|
|
|
|
(defcustom cider-known-endpoints nil
|
|
"Specify a list of custom endpoints where each endpoint is a list.
|
|
For example: '((\"label\" \"host\" \"port\")).
|
|
The label is optional so that '(\"host\" \"port\") will suffice.
|
|
This variable is used by `cider-connect'."
|
|
:type 'list
|
|
:group 'cider)
|
|
|
|
(defvar cider-ps-running-nrepls-command "ps u | grep leiningen"
|
|
"Process snapshot command used in `cider-locate-running-nrepl-ports'.")
|
|
|
|
(defvar cider-ps-running-nrepl-path-regexp-list
|
|
'("\\(?:leiningen.original.pwd=\\)\\(.+?\\) -D"
|
|
"\\(?:-classpath +:?\\(.+?\\)/self-installs\\)")
|
|
"Regexp list to extract project paths from output of `cider-ps-running-nrepls-command'.
|
|
Sub-match 1 must be the project path.")
|
|
|
|
(defvar cider-host-history nil
|
|
"Completion history for connection hosts.")
|
|
|
|
;;;###autoload
|
|
(defun cider-version ()
|
|
"Display CIDER's version."
|
|
(interactive)
|
|
(message "CIDER %s" (cider--version)))
|
|
|
|
;;;###autoload
|
|
(defun cider-jack-in (&optional prompt-project)
|
|
"Start a nREPL server for the current project and connect to it.
|
|
If PROMPT-PROJECT is t, then prompt for the project for which to
|
|
start the server."
|
|
(interactive "P")
|
|
(setq cider-current-clojure-buffer (current-buffer))
|
|
(if (cider--lein-present-p)
|
|
(let* ((project (when prompt-project
|
|
(read-directory-name "Project: ")))
|
|
(project-dir (nrepl-project-directory-for
|
|
(or project (nrepl-current-dir))))
|
|
(lein-params (if prompt-project
|
|
(read-string (format "nREPL server command: %s "
|
|
cider-lein-command)
|
|
cider-lein-parameters)
|
|
cider-lein-parameters))
|
|
(cmd (format "%s %s" cider-lein-command lein-params)))
|
|
(when (nrepl-check-for-repl-buffer nil project-dir)
|
|
(nrepl-start-server-process project-dir cmd)))
|
|
(message "The %s executable (specified by `cider-lein-command') isn't on your exec-path"
|
|
cider-lein-command)))
|
|
|
|
;;;###autoload
|
|
(defun cider-connect (host port)
|
|
"Connect to an nREPL server identified by HOST and PORT.
|
|
Create REPL buffer and start an nREPL client connection."
|
|
(interactive (cider-select-endpoint))
|
|
(setq cider-current-clojure-buffer (current-buffer))
|
|
(when (nrepl-check-for-repl-buffer `(,host ,port) nil)
|
|
(nrepl-start-client-process host port t)))
|
|
|
|
(defun cider-select-endpoint ()
|
|
"Interactively select the host and port to connect to."
|
|
(let* ((ssh-hosts (cider--ssh-hosts))
|
|
(hosts (-distinct (append (when cider-host-history
|
|
(list (list (car cider-host-history))))
|
|
(list (list (nrepl-current-host)))
|
|
cider-known-endpoints
|
|
ssh-hosts
|
|
(when (file-remote-p default-directory)
|
|
;; add localhost even in remote buffers
|
|
(list (list "localhost"))))))
|
|
(sel-host (cider--completing-read-host hosts))
|
|
(host (car sel-host))
|
|
(local-p (or (nrepl-local-host-p host)
|
|
(not (assoc-string host ssh-hosts))))
|
|
;; Each lein-port is a list of the form (dir port)
|
|
(lein-ports (if local-p
|
|
;; might connect to localhost from a remote file
|
|
(let* ((change-dir-p (file-remote-p default-directory))
|
|
(default-directory (if change-dir-p "~/" default-directory)))
|
|
(cider-locate-running-nrepl-ports (unless change-dir-p default-directory)))
|
|
(let ((vec (vector "ssh" nil host "" nil))
|
|
;; might connect to a different remote
|
|
(dir (when (file-remote-p default-directory)
|
|
(with-parsed-tramp-file-name default-directory cur
|
|
(when (string= cur-host host) default-directory)))))
|
|
(tramp-maybe-open-connection vec)
|
|
(with-current-buffer (tramp-get-connection-buffer vec)
|
|
(cider-locate-running-nrepl-ports dir)))))
|
|
(ports (append (cdr sel-host) lein-ports))
|
|
(port (cider--completing-read-port host ports)))
|
|
(list host port)))
|
|
|
|
(defun cider--ssh-hosts ()
|
|
"Retrieve all ssh host from local configuration files."
|
|
(-map (lambda (s) (list (replace-regexp-in-string ":$" "" s)))
|
|
(let ((tramp-completion-mode t))
|
|
(tramp-completion-handle-file-name-all-completions "" "/ssh:"))))
|
|
|
|
(defun cider--completing-read-host (hosts)
|
|
"Interactively select host from HOSTS.
|
|
Each element in HOSTS is one of: (host), (host port) or (label host port).
|
|
Return a list of the form (HOST PORT), where PORT can be nil."
|
|
(let* ((hosts (cider-join-into-alist hosts))
|
|
(sel-host (completing-read "Host: " hosts nil nil nil
|
|
'cider-host-history (caar hosts)))
|
|
(host (or (cdr (assoc sel-host hosts)) (list sel-host))))
|
|
;; remove the label
|
|
(if (= 3 (length host)) (cdr host) host)))
|
|
|
|
(defun cider--completing-read-port (host ports)
|
|
"Interactively select port for HOST from PORTS."
|
|
(let* ((ports (cider-join-into-alist ports))
|
|
(sel-port (completing-read (format "Port for %s: " host) ports
|
|
nil nil nil nil (caar ports)))
|
|
(port (or (cdr (assoc sel-port ports)) sel-port))
|
|
(port (if (listp port) (second port) port)))
|
|
(if (stringp port) (string-to-number port) port)))
|
|
|
|
(defun cider-locate-running-nrepl-ports (&optional dir)
|
|
"Locate ports of running nREPL servers.
|
|
When DIR is non-nil also look for nREPL port files in DIR. Return a list
|
|
of list of the form (project-dir port)."
|
|
(let* ((paths (cider--running-nrepl-paths))
|
|
(proj-ports (mapcar (lambda (d)
|
|
(-when-let (port (and d (nrepl-extract-port (cider--file-path d))))
|
|
(list (file-name-nondirectory (directory-file-name d)) port)))
|
|
(cons (nrepl-project-directory-for dir)
|
|
paths))))
|
|
(-distinct (delq nil proj-ports))))
|
|
|
|
(defun cider--running-nrepl-paths ()
|
|
"Retrieve project paths of running nREPL servers.
|
|
use `cider-ps-running-nrepls-command' and `cider-ps-running-nrepl-path-regexp-list'."
|
|
(let (paths)
|
|
(with-temp-buffer
|
|
(insert (shell-command-to-string cider-ps-running-nrepls-command))
|
|
(dolist (regexp cider-ps-running-nrepl-path-regexp-list)
|
|
(goto-char 1)
|
|
(while (re-search-forward regexp nil t)
|
|
(setq paths (cons (match-string 1) paths)))))
|
|
(-distinct paths)))
|
|
|
|
;; TODO: Implement a check for `cider-lein-command' over tramp
|
|
(defun cider--lein-present-p ()
|
|
"Check if `cider-lein-command' is on the `exec-path'.
|
|
|
|
In case `default-directory' is non-local we assume the command is available."
|
|
(or (file-remote-p default-directory)
|
|
(executable-find cider-lein-command)
|
|
(executable-find (concat cider-lein-command ".bat"))))
|
|
|
|
;;;###autoload
|
|
(eval-after-load 'clojure-mode
|
|
'(progn
|
|
(define-key clojure-mode-map (kbd "C-c M-j") 'cider-jack-in)
|
|
(define-key clojure-mode-map (kbd "C-c M-c") 'cider-connect)))
|
|
|
|
|
|
(define-obsolete-function-alias 'cider 'cider-connect)
|
|
|
|
(provide 'cider)
|
|
|
|
;;; cider.el ends here
|