Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

868 line
39 KiB

4 年之前
  1. ;;; cider-connection.el --- Connection and session life-cycle management for CIDER -*- lexical-binding: t -*-
  2. ;;
  3. ;; Copyright © 2019 Artur Malabarba, Bozhidar Batsov, Vitalie Spinu and CIDER contributors
  4. ;;
  5. ;; Author: Artur Malabarba <bruce.connor.am@gmail.com>
  6. ;; Bozhidar Batsov <bozhidar@batsov.com>
  7. ;; Vitalie Spinu <spinuvit@gmail.com>
  8. ;;
  9. ;; Keywords: languages, clojure, cider
  10. ;;
  11. ;; This program is free software: you can redistribute it and/or modify
  12. ;; it under the terms of the GNU General Public License as published by
  13. ;; the Free Software Foundation, either version 3 of the License, or
  14. ;; (at your option) any later version.
  15. ;;
  16. ;; This program is distributed in the hope that it will be useful,
  17. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. ;; GNU General Public License for more details.
  20. ;;
  21. ;; You should have received a copy of the GNU General Public License
  22. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. ;;
  24. ;; This file is not part of GNU Emacs.
  25. ;;
  26. ;;
  27. ;;; Commentary:
  28. ;;
  29. ;;
  30. ;;; Code:
  31. (require 'nrepl-client)
  32. (require 'cl-lib)
  33. (require 'format-spec)
  34. (require 'sesman)
  35. (require 'sesman-browser)
  36. (defcustom cider-session-name-template "%J:%h:%p"
  37. "Format string to use for session names.
  38. See `cider-format-connection-params' for available format characters."
  39. :type 'string
  40. :group 'cider
  41. :package-version '(cider . "0.18.0"))
  42. (defcustom cider-connection-message-fn #'cider-random-words-of-inspiration
  43. "The function to use to generate the message displayed on connect.
  44. When set to nil no additional message will be displayed. A good
  45. alternative to the default is `cider-random-tip'."
  46. :type 'function
  47. :group 'cider
  48. :package-version '(cider . "0.11.0"))
  49. (defcustom cider-redirect-server-output-to-repl t
  50. "Controls whether nREPL server output would be redirected to the REPL.
  51. When non-nil the output would end up in both the nrepl-server buffer (when
  52. available) and the matching REPL buffer."
  53. :type 'boolean
  54. :group 'cider
  55. :safe #'booleanp
  56. :package-version '(cider . "0.17.0"))
  57. (defcustom cider-auto-mode t
  58. "When non-nil, automatically enable cider mode for all Clojure buffers."
  59. :type 'boolean
  60. :group 'cider
  61. :safe #'booleanp
  62. :package-version '(cider . "0.9.0"))
  63. (defconst cider-required-nrepl-version "0.4.4"
  64. "The minimum nREPL version that's known to work properly with CIDER.")
  65. ;;; Connect
  66. (defun cider-nrepl-connect (params)
  67. "Start nrepl client and create the REPL.
  68. PARAMS is a plist containing :host, :port, :server and other parameters for
  69. `cider-repl-create'."
  70. (process-buffer
  71. (nrepl-start-client-process
  72. (plist-get params :host)
  73. (plist-get params :port)
  74. (plist-get params :server)
  75. (lambda (_)
  76. (cider-repl-create params)))))
  77. (defun cider-connected-p ()
  78. "Return t if CIDER is currently connected, nil otherwise."
  79. (process-live-p (get-buffer-process (cider-current-repl))))
  80. (defun cider-ensure-connected ()
  81. "Ensure there is a linked CIDER session."
  82. (sesman-ensure-session 'CIDER))
  83. (defun cider--session-server (session)
  84. "Return server buffer for SESSION or nil if there is no server."
  85. (seq-some (lambda (r)
  86. (buffer-local-value 'nrepl-server-buffer r))
  87. (cdr session)))
  88. (defun cider--gather-session-params (session)
  89. "Gather all params for a SESSION."
  90. (let (params)
  91. (dolist (repl (cdr session))
  92. (setq params (cider--gather-connect-params params repl)))
  93. (when-let* ((server (cider--session-server session)))
  94. (setq params (cider--gather-connect-params params server)))
  95. params))
  96. (defun cider--gather-connect-params (&optional params proc-buffer)
  97. "Gather all relevant connection parameters into PARAMS plist.
  98. PROC-BUFFER is either server or client buffer, defaults to current buffer."
  99. (let ((proc-buffer (or proc-buffer (current-buffer))))
  100. (with-current-buffer proc-buffer
  101. (unless nrepl-endpoint
  102. (error "This is not a REPL or SERVER buffer; is there an active REPL?"))
  103. (let ((server-buf (if (nrepl-server-p proc-buffer)
  104. proc-buffer
  105. nrepl-server-buffer)))
  106. (cl-loop for l on nrepl-endpoint by #'cddr
  107. do (setq params (plist-put params (car l) (cadr l))))
  108. (setq params (thread-first params
  109. (plist-put :project-dir nrepl-project-dir)))
  110. (when (buffer-live-p server-buf)
  111. (setq params (thread-first params
  112. (plist-put :server (get-buffer-process server-buf))
  113. (plist-put :server-command nrepl-server-command))))
  114. ;; repl-specific parameters (do not pollute server params!)
  115. (unless (nrepl-server-p proc-buffer)
  116. (setq params (thread-first params
  117. (plist-put :session-name cider-session-name)
  118. (plist-put :repl-type cider-repl-type)
  119. (plist-put :cljs-repl-type cider-cljs-repl-type)
  120. (plist-put :repl-init-function cider-repl-init-function))))
  121. params))))
  122. (defun cider--close-buffer (buffer)
  123. "Close the BUFFER and kill its associated process (if any)."
  124. (when (buffer-live-p buffer)
  125. (when-let* ((proc (get-buffer-process buffer)))
  126. (when (process-live-p proc)
  127. (delete-process proc)))
  128. (kill-buffer buffer)))
  129. (declare-function cider-repl-emit-interactive-stderr "cider-repl")
  130. (defun cider--close-connection (repl &optional no-kill)
  131. "Close connection associated with REPL.
  132. When NO-KILL is non-nil stop the connection but don't kill the REPL
  133. buffer."
  134. (when (buffer-live-p repl)
  135. (with-current-buffer repl
  136. (when spinner-current (spinner-stop))
  137. (when nrepl-tunnel-buffer
  138. (cider--close-buffer nrepl-tunnel-buffer))
  139. (when no-kill
  140. ;; inform sentinel not to kill the server, if any
  141. (thread-first (get-buffer-process repl)
  142. (process-plist)
  143. (plist-put :keep-server t))))
  144. (let ((proc (get-buffer-process repl)))
  145. (when (and (process-live-p proc)
  146. (or (not nrepl-server-buffer)
  147. ;; Sync request will hang if the server is dead.
  148. (process-live-p (get-buffer-process nrepl-server-buffer))))
  149. (nrepl-sync-request:close repl)
  150. (delete-process proc)))
  151. (when-let* ((messages-buffer (and nrepl-log-messages
  152. (nrepl-messages-buffer repl))))
  153. (kill-buffer messages-buffer))
  154. (unless no-kill
  155. (kill-buffer repl)))
  156. (when repl
  157. (sesman-remove-object 'CIDER nil repl (not no-kill) t)))
  158. (defun cider-emit-manual-warning (section-id format &rest args)
  159. "Emit a warning to the REPL and link to the online manual.
  160. SECTION-ID is the section to link to. The link is added on the last line.
  161. FORMAT is a format string to compile with ARGS and display on the REPL."
  162. (let ((message (apply #'format format args)))
  163. (cider-repl-emit-interactive-stderr
  164. (concat "WARNING: " message "\n "
  165. (cider--manual-button "More information" section-id)
  166. "."))))
  167. (defvar cider-version)
  168. (defun cider--check-required-nrepl-version ()
  169. "Check whether we're using a compatible nREPL version."
  170. (if-let* ((nrepl-version (cider--nrepl-version)))
  171. (when (version< nrepl-version cider-required-nrepl-version)
  172. (cider-emit-manual-warning "troubleshooting/#warning-saying-you-have-to-use-nrepl-0212"
  173. "CIDER requires nREPL %s (or newer) to work properly"
  174. cider-required-nrepl-version))
  175. (cider-emit-manual-warning "troubleshooting/#warning-saying-you-have-to-use-nrepl-0212"
  176. "Can't determine nREPL's version.\nPlease, update nREPL to %s."
  177. cider-required-nrepl-version)))
  178. (defvar cider-minimum-clojure-version)
  179. (defun cider--check-clojure-version-supported ()
  180. "Ensure that we are meeting the minimum supported version of Clojure."
  181. (if-let* ((clojure-version (cider--clojure-version)))
  182. (when (version< clojure-version cider-minimum-clojure-version)
  183. (cider-emit-manual-warning "installation/#prerequisites"
  184. "Clojure version (%s) is not supported (minimum %s). CIDER will not work."
  185. clojure-version cider-minimum-clojure-version))
  186. (cider-emit-manual-warning "installation/#prerequisites"
  187. "Can't determine Clojure's version. CIDER requires Clojure %s (or newer)."
  188. cider-minimum-clojure-version)))
  189. (defvar cider-required-middleware-version)
  190. (defun cider--check-middleware-compatibility ()
  191. "CIDER frontend/backend compatibility check.
  192. Retrieve the underlying connection's CIDER-nREPL version and checks if the
  193. middleware used is compatible with CIDER. If not, will display a warning
  194. message in the REPL area."
  195. (let* ((version-dict (nrepl-aux-info "cider-version" (cider-current-repl)))
  196. (middleware-version (nrepl-dict-get version-dict "version-string")))
  197. (cond
  198. ((null middleware-version)
  199. (cider-emit-manual-warning "troubleshooting/#cider-complains-of-the-cider-nrepl-version"
  200. "CIDER requires cider-nrepl to be fully functional. Some features will not be available without it!"))
  201. ((not (string= middleware-version cider-required-middleware-version))
  202. (cider-emit-manual-warning "troubleshooting/#cider-complains-of-the-cider-nrepl-version"
  203. "CIDER %s requires cider-nrepl %s, but you're currently using cider-nrepl %s. The version mismatch might break some functionality!"
  204. cider-version cider-required-middleware-version middleware-version)))))
  205. (declare-function cider-interactive-eval-handler "cider-eval")
  206. ;; TODO: Use some null handler here
  207. (defun cider--subscribe-repl-to-server-out ()
  208. "Subscribe to the nREPL server's *out*."
  209. (cider-nrepl-send-request '("op" "out-subscribe")
  210. (cider-interactive-eval-handler (current-buffer))))
  211. (declare-function cider-mode "cider-mode")
  212. (defun cider-enable-on-existing-clojure-buffers ()
  213. "Enable CIDER's minor mode on existing Clojure buffers.
  214. See command `cider-mode'."
  215. (interactive)
  216. (add-hook 'clojure-mode-hook #'cider-mode)
  217. (dolist (buffer (cider-util--clojure-buffers))
  218. (with-current-buffer buffer
  219. (cider-mode +1))))
  220. (defun cider-disable-on-existing-clojure-buffers ()
  221. "Disable command `cider-mode' on existing Clojure buffers."
  222. (interactive)
  223. (dolist (buffer (cider-util--clojure-buffers))
  224. (with-current-buffer buffer
  225. (cider-mode -1))))
  226. (defun cider-possibly-disable-on-existing-clojure-buffers ()
  227. "If not connected, disable command `cider-mode' on existing Clojure buffers."
  228. (unless (cider-connected-p)
  229. (cider-disable-on-existing-clojure-buffers)))
  230. (declare-function cider--debug-init-connection "cider-debug")
  231. (declare-function cider-repl-init "cider-repl")
  232. (defun cider--connected-handler ()
  233. "Handle CIDER initialization after nREPL connection has been established.
  234. This function is appended to `nrepl-connected-hook' in the client process
  235. buffer."
  236. ;; `nrepl-connected-hook' is run in the connection buffer
  237. ;; `cider-enlighten-mode' changes eval to include the debugger, so we inhibit
  238. ;; it here as the debugger isn't necessarily initialized yet
  239. (let ((cider-enlighten-mode nil))
  240. ;; after initialization, set mode-line and buffer name.
  241. (cider-set-repl-type cider-repl-type)
  242. (cider-repl-init
  243. (current-buffer)
  244. (lambda ()
  245. (cider--check-required-nrepl-version)
  246. (cider--check-clojure-version-supported)
  247. (cider--check-middleware-compatibility)
  248. (when cider-redirect-server-output-to-repl
  249. (cider--subscribe-repl-to-server-out))
  250. (when cider-auto-mode
  251. (cider-enable-on-existing-clojure-buffers))
  252. ;; Middleware on cider-nrepl's side is deferred until first usage, but
  253. ;; loading middleware concurrently can lead to occasional "require" issues
  254. ;; (likely a Clojure bug). Thus, we load the heavy debug middleware towards
  255. ;; the end, allowing for the faster "server-out" middleware to load
  256. ;; first.
  257. (cider--debug-init-connection)
  258. (when cider-repl-init-function
  259. (funcall cider-repl-init-function))
  260. (run-hooks 'cider-connected-hook)))))
  261. (defun cider--disconnected-handler ()
  262. "Cleanup after nREPL connection has been lost or closed.
  263. This function is appended to `nrepl-disconnected-hook' in the client
  264. process buffer."
  265. ;; `nrepl-connected-hook' is run in the connection buffer
  266. (cider-possibly-disable-on-existing-clojure-buffers)
  267. (run-hooks 'cider-disconnected-hook))
  268. ;;; Connection Info
  269. (defun cider--java-version ()
  270. "Retrieve the underlying connection's Java version."
  271. (with-current-buffer (cider-current-repl)
  272. (when nrepl-versions
  273. (thread-first nrepl-versions
  274. (nrepl-dict-get "java")
  275. (nrepl-dict-get "version-string")))))
  276. (defun cider--clojure-version ()
  277. "Retrieve the underlying connection's Clojure version."
  278. (with-current-buffer (cider-current-repl)
  279. (when nrepl-versions
  280. (thread-first nrepl-versions
  281. (nrepl-dict-get "clojure")
  282. (nrepl-dict-get "version-string")))))
  283. (defun cider--nrepl-version ()
  284. "Retrieve the underlying connection's nREPL version."
  285. (with-current-buffer (cider-current-repl)
  286. (when nrepl-versions
  287. (thread-first nrepl-versions
  288. (nrepl-dict-get "nrepl")
  289. (nrepl-dict-get "version-string")))))
  290. (defun cider--connection-info (connection-buffer &optional genericp)
  291. "Return info about CONNECTION-BUFFER.
  292. Info contains project name, current REPL namespace, host:port endpoint and
  293. Clojure version. When GENERICP is non-nil, don't provide specific info
  294. about this buffer (like variable `cider-repl-type')."
  295. (with-current-buffer connection-buffer
  296. (format "%s%s@%s:%s (Java %s, Clojure %s, nREPL %s)"
  297. (if genericp "" (upcase (concat (symbol-name cider-repl-type) " ")))
  298. (or (cider--project-name nrepl-project-dir) "<no project>")
  299. (plist-get nrepl-endpoint :host)
  300. (plist-get nrepl-endpoint :port)
  301. (cider--java-version)
  302. (cider--clojure-version)
  303. (cider--nrepl-version))))
  304. ;;; Cider's Connection Management UI
  305. (defun cider-quit (&optional repl)
  306. "Quit the CIDER connection associated with REPL.
  307. REPL defaults to the current REPL."
  308. (interactive)
  309. (let ((repl (or repl
  310. (sesman-browser-get 'object)
  311. (cider-current-repl nil 'ensure))))
  312. (cider--close-connection repl))
  313. ;; if there are no more connections we can kill all ancillary buffers
  314. (unless (cider-connected-p)
  315. (cider-close-ancillary-buffers))
  316. ;; need this to refresh sesman browser
  317. (run-hooks 'sesman-post-command-hook))
  318. (defun cider-restart (&optional repl)
  319. "Restart CIDER connection associated with REPL.
  320. REPL defaults to the current REPL. Don't restart the server or other
  321. connections within the same session. Use `sesman-restart' to restart the
  322. entire session."
  323. (interactive)
  324. (let* ((repl (or repl
  325. (sesman-browser-get 'object)
  326. (cider-current-repl nil 'ensure)))
  327. (params (thread-first ()
  328. (cider--gather-connect-params repl)
  329. (plist-put :session-name (sesman-session-name-for-object 'CIDER repl))
  330. (plist-put :repl-buffer repl))))
  331. (cider--close-connection repl 'no-kill)
  332. (cider-nrepl-connect params)
  333. ;; need this to refresh sesman browser
  334. (run-hooks 'sesman-post-command-hook)))
  335. (defun cider-close-ancillary-buffers ()
  336. "Close buffers that are shared across connections."
  337. (interactive)
  338. (dolist (buf-name cider-ancillary-buffers)
  339. (when (get-buffer buf-name)
  340. (kill-buffer buf-name))))
  341. (defun cider-describe-connection (&optional repl)
  342. "Display information about the connection associated with REPL.
  343. REPL defaults to the current REPL."
  344. (interactive)
  345. (let ((repl (or repl
  346. (sesman-browser-get 'object)
  347. (cider-current-repl nil 'ensure))))
  348. (message "%s" (cider--connection-info repl))))
  349. (define-obsolete-function-alias 'cider-display-connection-info 'cider-describe-connection "0.18.0")
  350. (defconst cider-nrepl-session-buffer "*cider-nrepl-session*")
  351. (defun cider-describe-nrepl-session ()
  352. "Describe an nREPL session."
  353. (interactive)
  354. (cider-ensure-connected)
  355. (let* ((repl (cider-current-repl nil 'ensure))
  356. (selected-session (completing-read "Describe nREPL session: " (nrepl-sessions repl))))
  357. (when (and selected-session (not (equal selected-session "")))
  358. (let* ((session-info (nrepl-sync-request:describe repl))
  359. (ops (nrepl-dict-keys (nrepl-dict-get session-info "ops")))
  360. (session-id (nrepl-dict-get session-info "session"))
  361. (session-type (cond
  362. ((equal session-id (cider-nrepl-eval-session)) "Active eval")
  363. ((equal session-id (cider-nrepl-tooling-session)) "Active tooling")
  364. (t "Unknown"))))
  365. (with-current-buffer (cider-popup-buffer cider-nrepl-session-buffer 'select nil 'ancillary)
  366. (read-only-mode -1)
  367. (insert (format "Session: %s\n" session-id)
  368. (format "Type: %s session\n" session-type)
  369. (format "Supported ops:\n"))
  370. (mapc (lambda (op) (insert (format " * %s\n" op))) ops)))
  371. (display-buffer cider-nrepl-session-buffer))))
  372. ;;; Sesman's Session-Wise Management UI
  373. (cl-defmethod sesman-project ((_system (eql CIDER)))
  374. (clojure-project-dir (cider-current-dir)))
  375. (cl-defmethod sesman-more-relevant-p ((_system (eql CIDER)) session1 session2)
  376. (sesman-more-recent-p (cdr session1) (cdr session2)))
  377. (cl-defmethod sesman-friendly-session-p ((_system (eql CIDER)) session)
  378. (setcdr session (seq-filter #'buffer-live-p (cdr session)))
  379. (when-let* ((repl (cadr session))
  380. (proc (get-buffer-process repl))
  381. (file (file-truename (or (buffer-file-name) default-directory))))
  382. ;; With avfs paths look like /path/to/.avfs/path/to/some.jar#uzip/path/to/file.clj
  383. (when (string-match-p "#uzip" file)
  384. (let ((avfs-path (directory-file-name (expand-file-name (or (getenv "AVFSBASE") "~/.avfs/")))))
  385. (setq file (replace-regexp-in-string avfs-path "" file t t))))
  386. (when (process-live-p proc)
  387. (let* ((classpath (or (process-get proc :cached-classpath)
  388. (let ((cp (with-current-buffer repl
  389. (cider-classpath-entries))))
  390. (process-put proc :cached-classpath cp)
  391. cp)))
  392. (classpath-roots (or (process-get proc :cached-classpath-roots)
  393. (let ((cp (thread-last classpath
  394. (seq-filter (lambda (path) (not (string-match-p "\\.jar$" path))))
  395. (mapcar #'file-name-directory))))
  396. (process-put proc :cached-classpath-roots cp)
  397. cp))))
  398. (or (seq-find (lambda (path) (string-prefix-p path file))
  399. classpath)
  400. (seq-find (lambda (path) (string-prefix-p path file))
  401. classpath-roots))))))
  402. (defvar cider-sesman-browser-map
  403. (let ((map (make-sparse-keymap)))
  404. (define-key map (kbd "j q") #'cider-quit)
  405. (define-key map (kbd "j k") #'cider-quit)
  406. (define-key map (kbd "j r") #'cider-restart)
  407. (define-key map (kbd "j d") #'cider-describe-connection)
  408. (define-key map (kbd "j i") #'cider-describe-connection)
  409. (define-key map (kbd "C-c C-q") #'cider-quit)
  410. (define-key map (kbd "C-c C-q") #'cider-quit)
  411. (define-key map (kbd "C-c C-r") #'cider-restart)
  412. (define-key map (kbd "C-c M-r") #'cider-restart)
  413. (define-key map (kbd "C-c C-d") #'cider-describe-connection)
  414. (define-key map (kbd "C-c M-d") #'cider-describe-connection)
  415. (define-key map (kbd "C-c C-i") #'cider-describe-connection)
  416. map)
  417. "Map active on REPL objects in sesman browser.")
  418. (cl-defmethod sesman-session-info ((_system (eql CIDER)) session)
  419. (interactive "P")
  420. (list :objects (cdr session)
  421. :map cider-sesman-browser-map))
  422. (declare-function cider "cider")
  423. (cl-defmethod sesman-start-session ((_system (eql CIDER)))
  424. "Start a connection of any type interactively.
  425. Fallback on `cider' command."
  426. (call-interactively #'cider))
  427. (cl-defmethod sesman-quit-session ((_system (eql CIDER)) session)
  428. (mapc #'cider--close-connection (cdr session))
  429. ;; if there are no more connections we can kill all ancillary buffers
  430. (unless (cider-connected-p)
  431. (cider-close-ancillary-buffers)))
  432. (cl-defmethod sesman-restart-session ((_system (eql CIDER)) session)
  433. (let* ((ses-name (car session))
  434. (repls (cdr session))
  435. (srv-buf (cider--session-server session)))
  436. (if srv-buf
  437. ;; session with a server
  438. (let ((s-params (cider--gather-connect-params nil srv-buf)))
  439. ;; 1) kill all connections, but keep the buffers
  440. (mapc (lambda (conn)
  441. (cider--close-connection conn 'no-kill))
  442. repls)
  443. ;; 2) kill the server
  444. (nrepl-kill-server-buffer srv-buf)
  445. ;; 3) start server
  446. (nrepl-start-server-process
  447. (plist-get s-params :project-dir)
  448. (plist-get s-params :server-command)
  449. (lambda (server-buf)
  450. ;; 4) restart the repls reusing the buffer
  451. (dolist (r repls)
  452. (cider-nrepl-connect
  453. (thread-first ()
  454. (cider--gather-connect-params r)
  455. ;; server params (:port, :project-dir etc) have precedence
  456. (cider--gather-connect-params server-buf)
  457. (plist-put :session-name ses-name)
  458. (plist-put :repl-buffer r))))
  459. (sesman-browser-revert-all 'CIDER)
  460. (message "Restarted CIDER %s session" ses-name))))
  461. ;; server-less session
  462. (dolist (r repls)
  463. (cider--close-connection r 'no-kill)
  464. (cider-nrepl-connect
  465. (thread-first ()
  466. (cider--gather-connect-params r)
  467. (plist-put :session-name ses-name)
  468. (plist-put :repl-buffer r)))))))
  469. (defun cider-format-connection-params (template params)
  470. "Format PARAMS with TEMPLATE string.
  471. The following formats can be used in TEMPLATE string:
  472. %h - host
  473. %H - remote host, empty for local hosts
  474. %p - port
  475. %j - short project name, or directory name if no project
  476. %J - long project name including parent dir name
  477. %r - REPL type (clj or cljs)
  478. %S - type of the ClojureScript runtime (Nashorn, Node, Figwheel etc.)
  479. %s - session name as defined by `cider-session-name-template'.
  480. In case some values are empty, extra separators (: and -) are automatically
  481. removed."
  482. (let* ((dir (directory-file-name
  483. (abbreviate-file-name
  484. (or (plist-get params :project-dir)
  485. (clojure-project-dir (cider-current-dir))
  486. default-directory))))
  487. (short-proj (file-name-nondirectory (directory-file-name dir)))
  488. (parent-dir (ignore-errors
  489. (thread-first dir file-name-directory
  490. directory-file-name file-name-nondirectory
  491. file-name-as-directory)))
  492. (long-proj (format "%s%s" (or parent-dir "") short-proj))
  493. ;; use `dir` if it is shorter than `long-proj` or `short-proj`
  494. (short-proj (if (>= (length short-proj) (length dir))
  495. dir
  496. short-proj))
  497. (long-proj (if (>= (length long-proj) (length dir))
  498. dir
  499. long-proj))
  500. (port (or (plist-get params :port) ""))
  501. (host (or (plist-get params :host) "localhost"))
  502. (remote-host (if (member host '("localhost" "127.0.0.1"))
  503. ""
  504. host))
  505. (repl-type (or (plist-get params :repl-type) "unknown"))
  506. (cljs-repl-type (or (and (eq repl-type 'cljs)
  507. (plist-get params :cljs-repl-type))
  508. ""))
  509. (specs `((?h . ,host)
  510. (?H . ,remote-host)
  511. (?p . ,port)
  512. (?j . ,short-proj)
  513. (?J . ,long-proj)
  514. (?r . ,repl-type)
  515. (?S . ,cljs-repl-type)))
  516. (ses-name (or (plist-get params :session-name)
  517. (format-spec cider-session-name-template specs)))
  518. (specs (append `((?s . ,ses-name)) specs)))
  519. (thread-last (format-spec template specs)
  520. ;; remove extraneous separators
  521. (replace-regexp-in-string "\\([:-]\\)[:-]+" "\\1")
  522. (replace-regexp-in-string "\\(^[:-]\\)\\|\\([:-]$\\)" "")
  523. (replace-regexp-in-string "[:-]\\([])*]\\)" "\\1"))))
  524. (defun cider-make-session-name (params)
  525. "Create new session name given plist of connection PARAMS.
  526. Session name can be customized with `cider-session-name-template'."
  527. (let* ((root-name (cider-format-connection-params cider-session-name-template params))
  528. (other-names (mapcar #'car (sesman-sessions 'CIDER)))
  529. (name root-name)
  530. (i 2))
  531. (while (member name other-names)
  532. (setq name (concat root-name "#" (number-to-string i))
  533. i (+ i 1)))
  534. name))
  535. ;;; REPL Buffer Init
  536. (defvar-local cider-cljs-repl-type nil
  537. "The type of the ClojureScript runtime (Nashorn, Node etc.)")
  538. (defvar-local cider-repl-type nil
  539. "The type of this REPL buffer, usually either clj or cljs.")
  540. (defun cider-repl-type (repl-buffer)
  541. "Get REPL-BUFFER's type."
  542. (buffer-local-value 'cider-repl-type repl-buffer))
  543. (defun cider-repl-type-for-buffer (&optional buffer)
  544. "Return the matching connection type (clj or cljs) for BUFFER.
  545. BUFFER defaults to the `current-buffer'. In cljc buffers return
  546. multi. This function infers connection type based on the major mode.
  547. For the REPL type use the function `cider-repl-type'."
  548. (with-current-buffer (or buffer (current-buffer))
  549. (cond
  550. ((derived-mode-p 'clojurescript-mode) 'cljs)
  551. ((derived-mode-p 'clojurec-mode) 'multi)
  552. ((derived-mode-p 'clojure-mode) 'clj)
  553. (cider-repl-type))))
  554. (defun cider-set-repl-type (&optional type)
  555. "Set REPL TYPE to clj or cljs.
  556. Assume that the current buffer is a REPL."
  557. (interactive)
  558. (let ((type (cider-maybe-intern (or type (completing-read
  559. (format "Set REPL type (currently `%s') to: "
  560. cider-repl-type)
  561. '(clj cljs))))))
  562. (when (or (not (equal cider-repl-type type))
  563. (null mode-name))
  564. (setq cider-repl-type type)
  565. (setq mode-name (format "REPL[%s]" type))
  566. (let ((params (cider--gather-connect-params)))
  567. ;; We need to set current name to something else temporarily to avoid
  568. ;; false name duplication in `nrepl-repl-buffer-name`.
  569. (rename-buffer (generate-new-buffer-name "*dummy-cider-repl-buffer*"))
  570. (rename-buffer (nrepl-repl-buffer-name params))
  571. (when (and nrepl-log-messages nrepl-messages-buffer)
  572. (with-current-buffer nrepl-messages-buffer
  573. (rename-buffer (nrepl-messages-buffer-name params))))))))
  574. (defun cider--choose-reusable-repl-buffer (params)
  575. "Find connection-less REPL buffer and ask the user for confirmation.
  576. Return nil if no such buffers exists or the user has chosen not to reuse
  577. the buffer. If multiple dead REPLs exist, ask the user to choose one.
  578. PARAMS is a plist as received by `cider-repl-create'."
  579. (when-let* ((repls (seq-filter (lambda (b)
  580. (with-current-buffer b
  581. (and (derived-mode-p 'cider-repl-mode)
  582. (not (process-live-p (get-buffer-process b))))))
  583. (buffer-list))))
  584. (let* ((proj-dir (plist-get params :project-dir))
  585. (host (plist-get params :host))
  586. (port (plist-get params :port))
  587. (cljsp (member (plist-get params :repl-type) '(cljs pending-cljs)))
  588. (scored-repls
  589. (delq nil
  590. (mapcar (lambda (b)
  591. (let ((bparams (cider--gather-connect-params nil b)))
  592. (when (eq cljsp (member (plist-get bparams :repl-type)
  593. '(cljs pending-cljs)))
  594. (cons (buffer-name b)
  595. (+
  596. (if (equal proj-dir (plist-get bparams :project-dir)) 8 0)
  597. (if (equal host (plist-get bparams :host)) 4 0)
  598. (if (equal port (plist-get bparams :port)) 2 0))))))
  599. repls))))
  600. (when scored-repls
  601. (if (> (length scored-repls) 1)
  602. (when (y-or-n-p "Dead REPLs exist. Reuse? ")
  603. (let ((sorted-repls (seq-sort (lambda (a b) (> (cdr a) (cdr b))) scored-repls)))
  604. (get-buffer (completing-read "REPL to reuse: "
  605. (mapcar #'car sorted-repls) nil t nil nil (caar sorted-repls)))))
  606. (when (y-or-n-p (format "A dead REPL %s exists. Reuse? " (caar scored-repls)))
  607. (get-buffer (caar scored-repls))))))))
  608. (declare-function cider-default-err-handler "cider-eval")
  609. (declare-function cider-repl-mode "cider-repl")
  610. (declare-function cider-repl--state-handler "cider-repl")
  611. (declare-function cider-repl-reset-markers "cider-repl")
  612. (defvar-local cider-session-name nil)
  613. (defvar-local cider-repl-init-function nil)
  614. (defun cider-repl-create (params)
  615. "Create new repl buffer.
  616. PARAMS is a plist which contains :repl-type, :host, :port, :project-dir,
  617. :repl-init-function and :session-name. When non-nil, :repl-init-function
  618. must be a function with no arguments which is called after repl creation
  619. function with the repl buffer set as current."
  620. ;; Connection might not have been set as yet. Please don't send requests in
  621. ;; this function, but use cider--connected-handler instead.
  622. (let ((buffer (or (plist-get params :repl-buffer)
  623. (cider--choose-reusable-repl-buffer params)
  624. (get-buffer-create (generate-new-buffer-name "*cider-uninitialized-repl*"))))
  625. (ses-name (or (plist-get params :session-name)
  626. (cider-make-session-name params))))
  627. (with-current-buffer buffer
  628. (setq-local sesman-system 'CIDER)
  629. (setq-local default-directory (or (plist-get params :project-dir) default-directory))
  630. ;; creates a new session if session with ses-name doesn't already exist
  631. (sesman-add-object 'CIDER ses-name buffer 'allow-new)
  632. (unless (derived-mode-p 'cider-repl-mode)
  633. (cider-repl-mode))
  634. (setq nrepl-err-handler #'cider-default-err-handler
  635. ;; used as a new-repl marker in cider-set-repl-type
  636. mode-name nil
  637. cider-session-name ses-name
  638. nrepl-project-dir (plist-get params :project-dir)
  639. ;; REPLs start with clj and then "upgrade" to a different type
  640. cider-repl-type (plist-get params :repl-type)
  641. ;; ran at the end of cider--connected-handler
  642. cider-repl-init-function (plist-get params :repl-init-function))
  643. (cider-repl-reset-markers)
  644. (add-hook 'nrepl-response-handler-functions #'cider-repl--state-handler nil 'local)
  645. (add-hook 'nrepl-connected-hook #'cider--connected-handler nil 'local)
  646. (add-hook 'nrepl-disconnected-hook #'cider--disconnected-handler nil 'local)
  647. (current-buffer))))
  648. ;;; Current/other REPLs
  649. (defun cider--no-repls-user-error (type)
  650. "Throw \"No REPL\" user error customized for TYPE."
  651. (let ((type (cond
  652. ((or (eq type 'multi) (eq type 'any))
  653. "clj or cljs")
  654. ((listp type)
  655. (mapconcat #'identity type " or "))
  656. (type))))
  657. (user-error "No %s REPLs in current session \"%s\""
  658. type (car (sesman-current-session 'CIDER)))))
  659. (defun cider-current-repl (&optional type ensure)
  660. "Get the most recent REPL of TYPE from the current session.
  661. TYPE is either clj, cljs, multi or any.
  662. When nil, infer the type from the current buffer.
  663. If ENSURE is non-nil, throw an error if either there is
  664. no linked session or there is no REPL of TYPE within the current session."
  665. (let ((type (cider-maybe-intern type)))
  666. (if (and (derived-mode-p 'cider-repl-mode)
  667. (or (null type)
  668. (eq 'any type)
  669. (eq cider-repl-type type)))
  670. ;; shortcut when in REPL buffer
  671. (current-buffer)
  672. (let* ((type (or type (cider-repl-type-for-buffer)))
  673. (repls (cider-repls type ensure))
  674. (repl (if (<= (length repls) 1)
  675. (car repls)
  676. ;; pick the most recent one
  677. (seq-find (lambda (b)
  678. (member b repls))
  679. (buffer-list)))))
  680. (if (and ensure (null repl))
  681. (cider--no-repls-user-error type)
  682. repl)))))
  683. (defun cider--match-repl-type (type buffer)
  684. "Return non-nil if TYPE matches BUFFER's REPL type."
  685. (let ((buffer-repl-type (cider-repl-type buffer)))
  686. (cond ((null buffer-repl-type) nil)
  687. ((or (null type) (eq type 'multi) (eq type 'any)) t)
  688. ((listp type) (member buffer-repl-type type))
  689. (t (string= type buffer-repl-type)))))
  690. (defun cider-repls (&optional type ensure)
  691. "Return cider REPLs of TYPE from the current session.
  692. If TYPE is nil or multi, return all repls. If TYPE is a list of types,
  693. return only REPLs of type contained in the list. If ENSURE is non-nil,
  694. throw an error if no linked session exists."
  695. (let ((type (cond
  696. ((listp type)
  697. (mapcar #'cider-maybe-intern type))
  698. ((cider-maybe-intern type))))
  699. (repls (cdr (if ensure
  700. (sesman-ensure-session 'CIDER)
  701. (sesman-current-session 'CIDER)))))
  702. (or (seq-filter (lambda (b)
  703. (cider--match-repl-type type b))
  704. repls)
  705. (when ensure
  706. (cider--no-repls-user-error type)))))
  707. (defun cider-map-repls (which function)
  708. "Call FUNCTION once for each appropriate REPL as indicated by WHICH.
  709. The function is called with one argument, the REPL buffer. The appropriate
  710. connections are found by inspecting the current buffer. WHICH is one of
  711. the following keywords:
  712. :auto - Act on the connections whose type matches the current buffer. In
  713. `cljc' files, mapping happens over both types of REPLs.
  714. :clj (:cljs) - Map over clj (cljs)) REPLs only.
  715. :clj-strict (:cljs-strict) - Map over clj (cljs) REPLs but signal a
  716. `user-error' in `clojurescript-mode' (`clojure-mode'). Use this for
  717. commands only supported in Clojure (ClojureScript).
  718. Error is signaled if no REPL buffers of specified type exist in current
  719. session."
  720. (declare (indent 1))
  721. (let ((cur-type (cider-repl-type-for-buffer)))
  722. (cl-case which
  723. (:clj-strict (when (eq cur-type 'cljs)
  724. (user-error "Clojure-only operation requested in a ClojureScript buffer")))
  725. (:cljs-strict (when (eq cur-type 'clj)
  726. (user-error "ClojureScript-only operation requested in a Clojure buffer"))))
  727. (let* ((type (cl-case which
  728. ((:clj :clj-strict) 'clj)
  729. ((:cljs :cljs-strict) 'cljs)
  730. (:auto (if (eq cur-type 'multi)
  731. '(clj cljs)
  732. cur-type))))
  733. (ensure (cl-case which
  734. (:auto nil)
  735. (t 'ensure)))
  736. (repls (cider-repls type ensure)))
  737. (mapcar function repls))))
  738. ;; REPLs double as connections in CIDER, so it's useful to be able to refer to
  739. ;; them as connections in certain contexts.
  740. (defalias 'cider-current-connection #'cider-current-repl)
  741. (defalias 'cider-connections #'cider-repls)
  742. (defalias 'cider-map-connections #'cider-map-repls)
  743. (defalias 'cider-connection-type-for-buffer #'cider-repl-type-for-buffer)
  744. ;; Deprecation after #2324
  745. (define-obsolete-function-alias 'cider-current-repl-buffer 'cider-current-repl "0.18")
  746. (define-obsolete-function-alias 'cider-repl-buffers 'cider-repls "0.18")
  747. (define-obsolete-function-alias 'cider-current-session 'cider-nrepl-eval-session "0.18")
  748. (define-obsolete-function-alias 'cider-current-tooling-session 'cider-nrepl-tooling-session "0.18")
  749. (define-obsolete-function-alias 'cider-display-connection-info 'cider-describe-connection "0.18")
  750. (define-obsolete-function-alias 'nrepl-connection-buffer-name 'nrepl-repl-buffer-name "0.18")
  751. (define-obsolete-function-alias 'cider-repl-set-type 'cider-set-repl-type "0.18")
  752. (make-obsolete 'cider-assoc-buffer-with-connection 'sesman-link-with-buffer "0.18")
  753. (make-obsolete 'cider-assoc-project-with-connection 'sesman-link-with-project "0.18")
  754. (make-obsolete 'cider-change-buffers-designation nil "0.18")
  755. (make-obsolete 'cider-clear-buffer-local-connection nil "0.18")
  756. (make-obsolete 'cider-close-nrepl-session 'cider-quit "0.18")
  757. (make-obsolete 'cider-create-sibling-cljs-repl 'cider-connect-sibling-cljs "0.18")
  758. (make-obsolete 'cider-current-messages-buffer nil "0.18")
  759. (make-obsolete 'cider-default-connection "see sesman." "0.18")
  760. (make-obsolete 'cider-extract-designation-from-current-repl-buffer nil "0.18")
  761. (make-obsolete 'cider-find-connection-buffer-for-project-directory 'sesman-current-sessions "0.18")
  762. (make-obsolete 'cider-find-reusable-repl-buffer nil "0.18")
  763. (make-obsolete 'cider-make-connection-default "see sesman." "0.18")
  764. (make-obsolete 'cider-other-connection nil "0.18")
  765. (make-obsolete 'cider-project-connections 'sesman-current-sessions "0.18")
  766. (make-obsolete 'cider-project-connections-types nil "0.18")
  767. (make-obsolete 'cider-prompt-for-project-on-connect nil "0.18")
  768. (make-obsolete 'cider-read-connection `sesman-ask-for-session "0.18")
  769. (make-obsolete 'cider-replicate-connection nil "0.18")
  770. (make-obsolete 'cider-request-dispatch "see sesman." "0.18")
  771. (make-obsolete 'cider-rotate-default-connection "see sesman." "0.18")
  772. (make-obsolete 'cider-toggle-buffer-connection nil "0.18")
  773. (make-obsolete 'cider-toggle-request-dispatch nil "0.18")
  774. (make-obsolete 'nrepl-connection-buffer-name-template 'nrepl-repl-buffer-name-template "0.18")
  775. (make-obsolete 'nrepl-create-client-buffer-function nil "0.18")
  776. (make-obsolete 'nrepl-post-client-callback nil "0.18")
  777. (make-obsolete 'nrepl-prompt-to-kill-server-buffer-on-quit nil "0.18")
  778. (make-obsolete 'nrepl-use-this-as-repl-buffer nil "0.18")
  779. ;; connection manager
  780. (make-obsolete 'cider-client-name-repl-type "see sesman." "0.18")
  781. (make-obsolete 'cider-connection-browser "see sesman." "0.18")
  782. (make-obsolete 'cider-connections-buffer-mode "see sesman." "0.18")
  783. (make-obsolete 'cider-connections-buffer-mode-map "see sesman." "0.18")
  784. (make-obsolete 'cider-connections-close-connection "see sesman." "0.18")
  785. (make-obsolete 'cider-connections-goto-connection "see sesman." "0.18")
  786. (make-obsolete 'cider-connections-make-default "see sesman." "0.18")
  787. (make-obsolete 'cider-display-connected-message "see sesman." "0.18")
  788. (make-obsolete 'cider-project-name "see sesman." "0.18")
  789. (provide 'cider-connection)
  790. ;;; cider-connection.el ends here