cider.el (85493B)
1 ;;; cider.el --- Clojure Interactive Development Environment that Rocks -*- lexical-binding: t -*- 2 3 ;; Copyright © 2012-2013 Tim King, Phil Hagelberg, Bozhidar Batsov 4 ;; Copyright © 2013-2023 Bozhidar Batsov, Artur Malabarba and CIDER contributors 5 ;; 6 ;; Author: Tim King <kingtim@gmail.com> 7 ;; Phil Hagelberg <technomancy@gmail.com> 8 ;; Bozhidar Batsov <bozhidar@batsov.dev> 9 ;; Artur Malabarba <bruce.connor.am@gmail.com> 10 ;; Hugo Duncan <hugo@hugoduncan.org> 11 ;; Steve Purcell <steve@sanityinc.com> 12 ;; Maintainer: Bozhidar Batsov <bozhidar@batsov.dev> 13 ;; URL: http://www.github.com/clojure-emacs/cider 14 ;; Version: 1.7.0 15 ;; Package-Requires: ((emacs "26") (clojure-mode "5.16.0") (parseedn "1.0.6") (queue "0.2") (spinner "1.7") (seq "2.22") (sesman "0.3.2")) 16 ;; Keywords: languages, clojure, cider 17 18 ;; This program is free software: you can redistribute it and/or modify 19 ;; it under the terms of the GNU General Public License as published by 20 ;; the Free Software Foundation, either version 3 of the License, or 21 ;; (at your option) any later version. 22 23 ;; This program is distributed in the hope that it will be useful, 24 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 25 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 ;; GNU General Public License for more details. 27 28 ;; You should have received a copy of the GNU General Public License 29 ;; along with this program. If not, see <http://www.gnu.org/licenses/>. 30 31 ;; This file is not part of GNU Emacs. 32 33 ;;; Commentary: 34 35 ;; Provides a Clojure interactive development environment for Emacs, built on 36 ;; top of nREPL. See https://docs.cider.mx for more details. 37 38 ;;; Installation: 39 40 ;; CIDER is available as a package in melpa.org and stable.melpa.org. First, make sure you've 41 ;; enabled one of the repositories in your Emacs config: 42 43 ;; (add-to-list 'package-archives 44 ;; '("melpa" . "https://melpa.org/packages/")) 45 ;; 46 ;; or 47 ;; 48 ;; (add-to-list 'package-archives 49 ;; '("melpa-stable" . "https://stable.melpa.org/packages/") t) 50 51 ;; Afterwards, installing CIDER is as easy as: 52 53 ;; M-x package-install cider 54 55 ;;; Usage: 56 57 ;; You can start CIDER with one of the following commands: 58 59 ;; M-x cider-jack-in-clj 60 ;; M-x cider-jack-in-cljs 61 ;; 62 ;; M-x cider-connect-sibling-clj 63 ;; M-x cider-connect-sibling-cljs 64 ;; 65 ;; M-x cider-connect-clj 66 ;; M-x cider-connect-cljs 67 68 ;;; Code: 69 70 (defgroup cider nil 71 "Clojure Interactive Development Environment that Rocks." 72 :prefix "cider-" 73 :group 'applications 74 :link '(url-link :tag "GitHub" "https://github.com/clojure-emacs/cider") 75 :link '(url-link :tag "Homepage" "https://cider.mx") 76 :link '(url-link :tag "Documentation" "https://docs.cider.mx") 77 :link '(emacs-commentary-link :tag "Commentary" "cider")) 78 79 (require 'cider-client) 80 (require 'cider-eldoc) 81 (require 'cider-repl) 82 (require 'cider-repl-history) 83 (require 'cider-connection) 84 (require 'cider-mode) 85 (require 'cider-common) 86 (require 'cider-debug) 87 (require 'cider-util) 88 89 (require 'cl-lib) 90 (require 'tramp-sh) 91 (require 'subr-x) 92 (require 'seq) 93 (require 'sesman) 94 (require 'package) 95 96 (defconst cider-version "1.7.0" 97 "The current version of CIDER.") 98 99 (defconst cider-codename "Côte d'Azur" 100 "Codename used to denote stable releases.") 101 102 (defcustom cider-lein-command 103 "lein" 104 "The command used to execute Leiningen." 105 :type 'string) 106 107 (defcustom cider-lein-global-options 108 nil 109 "Command global options used to execute Leiningen (e.g.: -o for offline)." 110 :type 'string 111 :safe #'stringp) 112 113 (defcustom cider-lein-parameters 114 "repl :headless :host localhost" 115 "Params passed to Leiningen to start an nREPL server via `cider-jack-in'." 116 :type 'string 117 :safe #'stringp) 118 119 (defcustom cider-boot-command 120 "boot" 121 "The command used to execute Boot." 122 :type 'string 123 :package-version '(cider . "0.9.0")) 124 125 (defcustom cider-boot-global-options 126 nil 127 "Command global options used to execute Boot (e.g.: -c for checkouts)." 128 :type 'string 129 :safe #'stringp 130 :package-version '(cider . "0.14.0")) 131 132 (defcustom cider-boot-parameters 133 "repl -s -b localhost wait" 134 "Params passed to boot to start an nREPL server via `cider-jack-in'." 135 :type 'string 136 :safe #'stringp 137 :package-version '(cider . "0.9.0")) 138 139 (defcustom cider-clojure-cli-command 140 (if (and (eq system-type 'windows-nt) 141 (null (executable-find "clojure"))) 142 "powershell" 143 "clojure") 144 "The command used to execute clojure with tools.deps (requires Clojure 1.9+). 145 Don't use clj here, as it doesn't work when spawned from Emacs due to it 146 using rlwrap. If on Windows and no \"clojure\" executable is found we 147 default to \"powershell\"." 148 :type 'string 149 :safe #'stringp 150 :package-version '(cider . "0.17.0")) 151 152 (defcustom cider-clojure-cli-global-options 153 nil 154 "Command line options used to execute clojure with tools.deps." 155 :type 'string 156 :safe #'stringp 157 :package-version '(cider . "0.17.0")) 158 159 (defcustom cider-clojure-cli-aliases 160 nil 161 "A list of aliases to include when using the clojure cli. 162 Alias names should be of the form \":foo:bar\". 163 Leading \"-A\" \"-M\" \"-T\" or \"-X\" are stripped from aliases 164 then concatenated into the \"-M[your-aliases]:cider/nrepl\" form." 165 :type 'string 166 :safe #'stringp 167 :package-version '(cider . "1.1")) 168 169 (defcustom cider-shadow-cljs-command 170 "npx shadow-cljs" 171 "The command used to execute shadow-cljs. 172 173 By default we favor the project-specific shadow-cljs over the system-wide." 174 :type 'string 175 :safe #'stringp 176 :package-version '(cider . "0.17.0")) 177 178 (defcustom cider-shadow-cljs-global-options 179 "" 180 "Command line options used to execute shadow-cljs (e.g.: -v for verbose mode)." 181 :type 'string 182 :safe #'stringp 183 :package-version '(cider . "0.17.0")) 184 185 (defcustom cider-shadow-cljs-parameters 186 "server" 187 "Params passed to shadow-cljs to start an nREPL server via `cider-jack-in'." 188 :type 'string 189 :safe #'stringp 190 :package-version '(cider . "0.17.0")) 191 192 (defcustom cider-gradle-command 193 "./gradlew" 194 "The command used to execute Gradle." 195 :type 'string 196 :safe #'stringp 197 :package-version '(cider . "0.10.0")) 198 199 (defcustom cider-gradle-global-options 200 "" 201 "Command line options used to execute Gradle (e.g.: -m for dry run)." 202 :type 'string 203 :safe #'stringp 204 :package-version '(cider . "0.14.0")) 205 206 (defcustom cider-gradle-parameters 207 "clojureRepl" 208 "Params passed to gradle to start an nREPL server via `cider-jack-in'." 209 :type 'string 210 :safe #'stringp 211 :package-version '(cider . "0.10.0")) 212 213 (defcustom cider-babashka-command 214 "bb" 215 "The command used to execute Babashka." 216 :type 'string 217 :safe #'stringp 218 :package-version '(cider . "1.2.0")) 219 220 (defcustom cider-babashka-global-options 221 nil 222 "Command line options used to execute Babashka." 223 :type 'string 224 :safe #'stringp 225 :package-version '(cider . "1.2.0")) 226 227 (defcustom cider-babashka-parameters 228 "nrepl-server localhost:0" 229 "Params passed to babashka to start an nREPL server via `cider-jack-in'." 230 :type 'string 231 :safe #'stringp 232 :package-version '(cider . "1.2.0")) 233 234 (defcustom cider-nbb-command 235 "nbb" 236 "The command used to execute nbb." 237 :type 'string 238 :safe #'stringp 239 :package-version '(cider . "1.6.0")) 240 241 (defcustom cider-nbb-global-options 242 nil 243 "Command line options used to execute nbb." 244 :type 'string 245 :safe #'stringp 246 :package-version '(cider . "1.6.0")) 247 248 (defcustom cider-nbb-parameters 249 "nrepl-server" 250 "Params passed to nbb to start an nREPL server via `cider-jack-in'." 251 :type 'string 252 :safe #'stringp 253 :package-version '(cider . "1.6.0")) 254 255 (defcustom cider-jack-in-default 256 (if (executable-find "clojure") 'clojure-cli 'lein) 257 "The default tool to use when doing `cider-jack-in' outside a project. 258 This value will only be consulted when no identifying file types, i.e. 259 project.clj for leiningen or build.boot for boot, could be found. 260 261 As the Clojure CLI is bundled with Clojure itself, it's the default. 262 In the absence of the Clojure CLI (e.g. on Windows), we fallback 263 to Leiningen." 264 :type '(choice (const lein) 265 (const boot) 266 (const clojure-cli) 267 (const shadow-cljs) 268 (const gradle) 269 (const babashka) 270 (const nbb)) 271 :safe #'symbolp 272 :package-version '(cider . "0.9.0")) 273 274 (defcustom cider-preferred-build-tool 275 nil 276 "Allow choosing a build system when there are many. 277 When there are project markers from multiple build systems (e.g. lein and 278 boot) the user is prompted to select one of them. When non-nil, this 279 variable will suppress this behavior and will select whatever build system 280 is indicated by the variable if present. Note, this is only when CIDER 281 cannot decide which of many build systems to use and will never override a 282 command when there is no ambiguity." 283 :type '(choice (const lein) 284 (const boot) 285 (const clojure-cli) 286 (const shadow-cljs) 287 (const gradle) 288 (const babashka) 289 (const nbb) 290 (const :tag "Always ask" nil)) 291 :safe #'symbolp 292 :package-version '(cider . "0.13.0")) 293 294 (defcustom cider-allow-jack-in-without-project 'warn 295 "Controls what happens when doing `cider-jack-in' outside a project. 296 When set to 'warn you'd prompted to confirm the command. 297 When set to t `cider-jack-in' will quietly continue. 298 When set to nil `cider-jack-in' will fail." 299 :type '(choice (const :tag "always" t) 300 (const warn) 301 (const :tag "never" nil)) 302 :safe #'symbolp 303 :package-version '(cider . "0.15.0")) 304 305 (defcustom cider-known-endpoints nil 306 "A list of connection endpoints where each endpoint is a list. 307 For example: \\='((\"label\" \"host\" \"port\")). 308 The label is optional so that \\='(\"host\" \"port\") will suffice. 309 This variable is used by `cider-connect'." 310 :type '(repeat (list (string :tag "label") 311 (string :tag "host") 312 (string :tag "port")))) 313 314 (defcustom cider-connected-hook nil 315 "List of functions to call when connected to Clojure nREPL server." 316 :type 'hook 317 :package-version '(cider . "0.9.0")) 318 319 (defcustom cider-disconnected-hook nil 320 "List of functions to call when disconnected from the Clojure nREPL server." 321 :type 'hook 322 :package-version '(cider . "0.9.0")) 323 324 (defcustom cider-inject-dependencies-at-jack-in t 325 "When nil, do not inject repl dependencies at `cider-jack-in' time. 326 The repl dependendcies are most likely to be nREPL middlewares." 327 :type 'boolean 328 :safe #'booleanp 329 :version '(cider . "0.11.0")) 330 331 (defcustom cider-offer-to-open-cljs-app-in-browser t 332 "When nil, do not offer to open ClojureScript apps in a browser on connect." 333 :type 'boolean 334 :safe #'booleanp 335 :version '(cider . "0.15.0")) 336 337 (defvar cider-ps-running-nrepls-command "ps u | grep leiningen" 338 "Process snapshot command used in `cider-locate-running-nrepl-ports'.") 339 340 (defvar cider-ps-running-nrepl-path-regexp-list 341 '("\\(?:leiningen.original.pwd=\\)\\(.+?\\) -D" 342 "\\(?:-classpath +:?\\(.+?\\)/self-installs\\)") 343 "Regexp list to get project paths. 344 Extract project paths from output of `cider-ps-running-nrepls-command'. 345 Sub-match 1 must be the project path.") 346 347 (defvar cider-host-history nil 348 "Completion history for connection hosts.") 349 350 (defvar cider-jack-in-universal-options 351 '((clojure-cli (:prefix-arg 1 :cmd (:jack-in-type clj :project-type clojure-cli :edit-project-dir t))) 352 (lein (:prefix-arg 2 :cmd (:jack-in-type clj :project-type lein :edit-project-dir t))) 353 (babashka (:prefix-arg 3 :cmd (:jack-in-type clj :project-type babashka :edit-project-dir t))) 354 (nbb (:prefix-arg 4 :cmd (:jack-in-type cljs :project-type nbb :cljs-repl-type nbb :edit-project-dir t)))) 355 "The list of project tools that are supported by the universal jack in command. 356 357 Each item in the list consists of the tool name and its plist options. 358 359 The plist supports the following keys 360 361 - :prefix-arg the numerical prefix arg to use to jack in to the tool. 362 363 - :cmd a plist of instructions how to invoke the jack in command, with keys 364 365 - :jack-in-type 'clj to start a clj repl and 'cljs for a cljs repl. 366 367 - &rest the same set of params supported by the `cider-jack-in-clj' and 368 `cider-jack-in-cljs' commands.") 369 370 ;;;###autoload 371 (defun cider-version () 372 "Display CIDER's version." 373 (interactive) 374 (message "CIDER %s" (cider--version))) 375 376 (defun cider-jack-in-command (project-type) 377 "Determine the command `cider-jack-in' needs to invoke for the PROJECT-TYPE." 378 (pcase project-type 379 ('lein cider-lein-command) 380 ('boot cider-boot-command) 381 ('clojure-cli cider-clojure-cli-command) 382 ('babashka cider-babashka-command) 383 ('shadow-cljs cider-shadow-cljs-command) 384 ('gradle cider-gradle-command) 385 ('nbb cider-nbb-command) 386 (_ (user-error "Unsupported project type `%S'" project-type)))) 387 388 (defun cider-jack-in-resolve-command (project-type) 389 "Determine the resolved file path to `cider-jack-in-command'. 390 Throws an error if PROJECT-TYPE is unknown." 391 (pcase project-type 392 ('lein (cider--resolve-command cider-lein-command)) 393 ('boot (cider--resolve-command cider-boot-command)) 394 ('clojure-cli (cider--resolve-command cider-clojure-cli-command)) 395 ('babashka (cider--resolve-command cider-babashka-command)) 396 ;; here we have to account for the possibility that the command is either 397 ;; "npx shadow-cljs" or just "shadow-cljs" 398 ('shadow-cljs (let ((parts (split-string cider-shadow-cljs-command))) 399 (when-let* ((command (cider--resolve-command (car parts)))) 400 (mapconcat #'identity (cons command (cdr parts)) " ")))) 401 ;; TODO: Address the duplicated code below. 402 ;; here we have to account for the possibility that the command is either 403 ;; "nbb" (default) or "npx nbb". 404 ('nbb (let ((parts (split-string cider-nbb-command))) 405 (when-let* ((command (cider--resolve-command (car parts)))) 406 (mapconcat #'identity (cons command (cdr parts)) " ")))) 407 ;; here we have to account for use of the Gradle wrapper which is 408 ;; a shell script within their project, so if they have a clearly 409 ;; relative path like "./gradlew" use locate file instead of checking 410 ;; the exec-path 411 ('gradle (cider--resolve-project-command cider-gradle-command)) 412 (_ (user-error "Unsupported project type `%S'" project-type)))) 413 414 (defun cider-jack-in-global-options (project-type) 415 "Determine the command line options for `cider-jack-in' for the PROJECT-TYPE." 416 (pcase project-type 417 ('lein cider-lein-global-options) 418 ('boot cider-boot-global-options) 419 ('clojure-cli cider-clojure-cli-global-options) 420 ('babashka cider-babashka-global-options) 421 ('shadow-cljs cider-shadow-cljs-global-options) 422 ('gradle cider-gradle-global-options) 423 ('nbb cider-nbb-global-options) 424 (_ (user-error "Unsupported project type `%S'" project-type)))) 425 426 (defun cider-jack-in-params (project-type) 427 "Determine the commands params for `cider-jack-in' for the PROJECT-TYPE." 428 ;; The format of these command-line strings must consider different shells, 429 ;; different values of IFS, and the possibility that they'll be run remotely 430 ;; (e.g. with TRAMP). Using `", "` causes problems with TRAMP, for example. 431 ;; Please be careful when changing them. 432 (pcase project-type 433 ('lein cider-lein-parameters) 434 ('boot cider-boot-parameters) 435 ('clojure-cli nil) 436 ('babashka cider-babashka-parameters) 437 ('shadow-cljs cider-shadow-cljs-parameters) 438 ('gradle cider-gradle-parameters) 439 ('nbb cider-nbb-parameters) 440 (_ (user-error "Unsupported project type `%S'" project-type)))) 441 442 443 ;;; Jack-in dependencies injection 444 (defvar cider-jack-in-dependencies nil 445 "List of dependencies where elements are lists of artifact name and version.") 446 (put 'cider-jack-in-dependencies 'risky-local-variable t) 447 448 (defcustom cider-injected-nrepl-version "1.0.0" 449 "The version of nREPL injected on jack-in. 450 We inject the newest known version of nREPL just in case 451 your version of Boot or Leiningen is bundling an older one." 452 :type 'string 453 :package-version '(cider . "1.2.0") 454 :safe #'stringp) 455 456 (defvar cider-jack-in-cljs-dependencies nil 457 "List of dependencies where elements are lists of artifact name and version. 458 Added to `cider-jack-in-dependencies' when doing `cider-jack-in-cljs'.") 459 (put 'cider-jack-in-cljs-dependencies 'risky-local-variable t) 460 (cider-add-to-alist 'cider-jack-in-cljs-dependencies "cider/piggieback" "0.5.2") 461 462 (defvar cider-jack-in-dependencies-exclusions nil 463 "List of exclusions for jack in dependencies. 464 Elements of the list are artifact name and list of exclusions to apply for 465 the artifact.") 466 (put 'cider-jack-in-dependencies-exclusions 'risky-local-variable t) 467 468 (defconst cider-clojure-artifact-id "org.clojure/clojure" 469 "Artifact identifier for Clojure.") 470 471 (defconst cider-minimum-clojure-version "1.8.0" 472 "Minimum supported version of Clojure.") 473 474 (defconst cider-latest-clojure-version "1.10.1" 475 "Latest supported version of Clojure.") 476 477 (defconst cider-required-middleware-version "0.30.0" 478 "The CIDER nREPL version that's known to work properly with CIDER.") 479 480 (defcustom cider-injected-middleware-version cider-required-middleware-version 481 "The version of cider-nrepl injected on jack-in. 482 Should be newer than the required version for optimal results." 483 :type 'string 484 :package-version '(cider . "1.2.0") 485 :safe #'stringp) 486 487 (defcustom cider-enrich-classpath nil 488 "Whether to use git.io/JiJVX for adding sources and javadocs to the classpath. 489 490 This is done in a clean manner, without interfering with classloaders. 491 492 Only available for Leiningen projects at the moment." 493 :type 'boolean 494 :package-version '(cider . "1.2.0") 495 :safe #'booleanp) 496 497 (defcustom cider-jack-in-auto-inject-clojure nil 498 "Version of clojure to auto-inject into REPL. 499 If nil, do not inject Clojure into the REPL. If `latest', inject 500 `cider-latest-clojure-version', which should approximate to the most recent 501 version of Clojure. If `minimal', inject `cider-minimum-clojure-version', 502 which will be the lowest version CIDER supports. If a string, use this as 503 the version number. If it is a list, the first element should be a string, 504 specifying the artifact ID, and the second element the version number." 505 :type '(choice (const :tag "None" nil) 506 (const :tag "Latest" latest) 507 (const :tag "Minimal" minimal) 508 (string :tag "Specific Version") 509 (list :tag "Artifact ID and Version" 510 (string :tag "Artifact ID") 511 (string :tag "Version")))) 512 513 (defvar-local cider-jack-in-cmd nil 514 "The custom command used to start a nrepl server. 515 This is used by `cider-jack-in`. 516 517 If this variable is set, its value will be 518 used as the command to start the nrepl server 519 instead of the default command inferred from 520 the project type. 521 522 This allows for fine-grained control over the jack-in process. 523 The value should be a string representing the command to start 524 the nrepl server, such as \"nbb nrepl-server\".") 525 526 (defvar cider-jack-in-lein-plugins nil 527 "List of Leiningen plugins to be injected at jack-in. 528 Each element is a list of artifact name and version, followed optionally by 529 keyword arguments. The only keyword argument currently accepted is 530 `:predicate', which should be given a function that takes the list (name, 531 version, and keyword arguments) and returns non-nil to indicate that the 532 plugin should actually be injected. (This is useful primarily for packages 533 that extend CIDER, not for users. For example, a refactoring package might 534 want to inject some middleware only when within a project context.)") 535 (put 'cider-jack-in-lein-plugins 'risky-local-variable t) 536 537 (defvar cider-jack-in-lein-middlewares nil 538 "List of Leiningen :middleware values to be injected at jack-in. 539 540 Necessary for plugins which require an explicit middleware name to be specified. 541 542 Can also facilitate using middleware in a specific order.") 543 (put 'cider-jack-in-lein-middlewares 'risky-local-variable t) 544 545 (defvar cider-jack-in-cljs-lein-plugins nil 546 "List of Leiningen plugins to be injected at jack-in. 547 Added to `cider-jack-in-lein-plugins' (which see) when doing 548 `cider-jack-in-cljs'.") 549 (put 'cider-jack-in-cljs-lein-plugins 'risky-local-variable t) 550 551 (defun cider-jack-in-normalized-lein-plugins () 552 "Return a normalized list of Leiningen plugins to be injected. 553 See `cider-jack-in-lein-plugins' for the format, except that the list 554 returned by this function does not include keyword arguments." 555 (let ((plugins (if cider-enrich-classpath 556 (append cider-jack-in-lein-plugins 557 `(("cider/cider-nrepl" ,cider-injected-middleware-version) 558 ("mx.cider/enrich-classpath" "1.9.0"))) 559 (append cider-jack-in-lein-plugins 560 `(("cider/cider-nrepl" ,cider-injected-middleware-version)))))) 561 (thread-last 562 plugins 563 (seq-filter 564 (lambda (spec) 565 (if-let* ((pred (plist-get (seq-drop spec 2) :predicate))) 566 (funcall pred spec) 567 t))) 568 (mapcar 569 (lambda (spec) 570 (seq-take spec 2)))))) 571 572 (defvar cider-jack-in-nrepl-middlewares nil 573 "List of Clojure variable names. 574 Each of these Clojure variables should hold a vector of nREPL middlewares. 575 Instead of a string, an element can be a list containing a string followed 576 by optional keyword arguments. The only keyword argument currently 577 accepted is `:predicate', which should be given a function that takes the 578 list (string and keyword arguments) and returns non-nil to indicate that 579 the middlewares should actually be injected.") 580 (put 'cider-jack-in-nrepl-middlewares 'risky-local-variable t) 581 (add-to-list 'cider-jack-in-nrepl-middlewares "cider.nrepl/cider-middleware") 582 583 (defvar cider-jack-in-cljs-nrepl-middlewares nil 584 "List of Clojure variable names. 585 Added to `cider-jack-in-nrepl-middlewares' (which see) when doing 586 `cider-jack-in-cljs'.") 587 (put 'cider-jack-in-cljs-nrepl-middlewares 'risky-local-variable t) 588 (add-to-list 'cider-jack-in-cljs-nrepl-middlewares "cider.piggieback/wrap-cljs-repl") 589 590 (defun cider-jack-in-normalized-nrepl-middlewares () 591 "Return a normalized list of middleware variable names. 592 See `cider-jack-in-nrepl-middlewares' for the format, except that the list 593 returned by this function only contains strings." 594 (thread-last 595 cider-jack-in-nrepl-middlewares 596 (seq-filter 597 (lambda (spec) 598 (or (not (listp spec)) 599 (if-let* ((pred (plist-get (cdr spec) :predicate))) 600 (funcall pred spec) 601 t)))) 602 (mapcar 603 (lambda (spec) 604 (if (listp spec) 605 (car spec) 606 spec))))) 607 608 (defun cider--list-as-boot-artifact (list) 609 "Return a boot artifact string described by the elements of LIST. 610 LIST should have the form (ARTIFACT-NAME ARTIFACT-VERSION). The returned 611 string is quoted for passing as argument to an inferior shell." 612 (concat "-d " (shell-quote-argument (format "%s:%s" (car list) (cadr list))))) 613 614 (defun cider--jack-in-required-dependencies () 615 "Returns the required CIDER deps. 616 They are normally added to `cider-jack-in-dependencies', 617 unless it's a Lein project." 618 `(("nrepl/nrepl" ,cider-injected-nrepl-version) 619 ("cider/cider-nrepl" ,cider-injected-middleware-version))) 620 621 (defun cider-boot-dependencies (dependencies) 622 "Return a list of boot artifact strings created from DEPENDENCIES." 623 (concat (mapconcat #'cider--list-as-boot-artifact dependencies " ") 624 (unless (seq-empty-p dependencies) " "))) 625 626 (defun cider-boot-middleware-task (params middlewares) 627 "Create a command to add MIDDLEWARES with corresponding PARAMS." 628 (concat "cider.tasks/add-middleware " 629 (mapconcat (lambda (middleware) 630 (format "-m %s" (shell-quote-argument middleware))) 631 middlewares 632 " ") 633 " " params)) 634 635 (defun cider-boot-jack-in-dependencies (global-opts params dependencies middlewares) 636 "Create boot jack-in dependencies. 637 Does so by concatenating GLOBAL-OPTS, DEPENDENCIES, 638 and MIDDLEWARES. PARAMS and MIDDLEWARES are passed on to 639 `cider-boot-middleware-task` before concatenating and DEPENDENCIES 640 are passed on to `cider-boot-dependencies`." 641 (concat global-opts 642 (unless (seq-empty-p global-opts) " ") 643 "-i \"(require 'cider.tasks)\" " ;; Note the space at the end here 644 (cider-boot-dependencies (append (cider--jack-in-required-dependencies) dependencies)) 645 (cider-boot-middleware-task params middlewares))) 646 647 (defun cider--gradle-dependency-notation (dependency) 648 "Returns Gradle's GAV dependency syntax. 649 For a \"group/artifact\" \"version\") DEPENDENCY list 650 return as group:artifact:version notation." 651 (let ((group-artifact (replace-regexp-in-string "/" ":" (car dependency))) 652 (version (cadr dependency))) 653 (format "%s:%s" group-artifact version))) 654 655 (defun cider--gradle-jack-in-property (dependencies) 656 "Returns Clojurephant's dependency jack-in property. 657 For DEPENDENCIES, translates to Gradle's dependency notation 658 using `cider--gradle-dependency-notation`.''" 659 (if (seq-empty-p dependencies) 660 "" 661 (shell-quote-argument 662 (concat "-Pdev.clojurephant.jack-in.nrepl=" 663 (mapconcat #'cider--gradle-dependency-notation dependencies ","))))) 664 665 (defun cider--gradle-middleware-params (middlewares) 666 "Returns Gradle-formatted middleware params. 667 Given a list of MIDDLEWARES symbols, this returns 668 the Gradle parameters expected by Clojurephant's 669 ClojureNRepl task." 670 (mapconcat (lambda (middleware) 671 (shell-quote-argument (concat "--middleware=" middleware))) 672 middlewares 673 " ")) 674 675 (defun cider-gradle-jack-in-dependencies (global-opts params dependencies middlewares) 676 "Create gradle jack in dependencies. 677 Does so by concatenating GLOBAL-OPTS, DEPENDENCIES, 678 and MIDDLEWARES. GLOBAL-OPTS and PARAMS are taken as-is. 679 DEPENDENCIES are translated into Gradle's typical 680 group:artifact:version notation and MIDDLEWARES are 681 prepared as arguments to Clojurephant's ClojureNRepl task." 682 (concat global-opts 683 (unless (seq-empty-p global-opts) " ") 684 (cider--gradle-jack-in-property (append (cider--jack-in-required-dependencies) dependencies)) 685 " " 686 params 687 (unless (seq-empty-p params) " ") 688 (cider--gradle-middleware-params middlewares))) 689 690 (defun cider--lein-artifact-exclusions (exclusions) 691 "Return an exclusions vector described by the elements of EXCLUSIONS." 692 (if exclusions 693 (format " :exclusions [%s]" (mapconcat #'identity exclusions " ")) 694 "")) 695 696 (defun cider--list-as-lein-artifact (list &optional exclusions) 697 "Return an artifact string described by the elements of LIST. 698 LIST should have the form (ARTIFACT-NAME ARTIFACT-VERSION). Optionally a list 699 of EXCLUSIONS can be provided as well. The returned 700 string is quoted for passing as argument to an inferior shell." 701 (shell-quote-argument (format "[%s %S%s]" (car list) (cadr list) (cider--lein-artifact-exclusions exclusions)))) 702 703 (defun cider-lein-jack-in-dependencies (global-opts params dependencies dependencies-exclusions lein-plugins &optional lein-middlewares) 704 "Create lein jack-in dependencies. 705 Does so by concatenating GLOBAL-OPTS, DEPENDENCIES, with DEPENDENCIES-EXCLUSIONS 706 removed, LEIN-PLUGINS, LEIN-MIDDLEWARES and finally PARAMS." 707 (concat 708 global-opts 709 (unless (seq-empty-p global-opts) " ") 710 (mapconcat #'identity 711 (append (seq-map (lambda (dep) 712 (let ((exclusions (cadr (assoc (car dep) dependencies-exclusions)))) 713 (concat "update-in :dependencies conj " 714 (cider--list-as-lein-artifact dep exclusions)))) 715 dependencies) 716 (seq-map (lambda (plugin) 717 (concat "update-in :plugins conj " 718 (cider--list-as-lein-artifact plugin))) 719 lein-plugins) 720 (seq-map (lambda (middleware) 721 (concat "update-in :middleware conj " 722 middleware)) 723 lein-middlewares)) 724 " -- ") 725 " -- " 726 params)) 727 728 (defun cider--dedupe-deps (deps) 729 "Removes the duplicates in DEPS." 730 (cl-delete-duplicates deps :test 'equal)) 731 732 (defun cider-clojure-cli-jack-in-dependencies (global-options _params dependencies) 733 "Create Clojure tools.deps jack-in dependencies. 734 Does so by concatenating DEPENDENCIES and GLOBAL-OPTIONS into a suitable 735 `clojure` invocation. The main is placed in an inline alias :cider/nrepl 736 so that if your aliases contain any mains, the cider/nrepl one will be the 737 one used." 738 (let* ((all-deps (thread-last 739 dependencies 740 (append (cider--jack-in-required-dependencies)) 741 ;; Duplicates are never OK since they would result in 742 ;; `java.lang.IllegalArgumentException: Duplicate key [...]`: 743 (cider--dedupe-deps) 744 (seq-map (lambda (dep) 745 (if (listp (cadr dep)) 746 (format "%s {%s}" 747 (car dep) 748 (seq-reduce 749 (lambda (acc v) 750 (concat acc (format " :%s \"%s\" " (car v) (cdr v)))) 751 (cadr dep) 752 "")) 753 (format "%s {:mvn/version \"%s\"}" (car dep) (cadr dep))))))) 754 (middleware (mapconcat 755 (apply-partially #'format "%s") 756 (cider-jack-in-normalized-nrepl-middlewares) 757 ",")) 758 (main-opts (format "\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[%s]\"" middleware))) 759 (format "%s-Sdeps '{:deps {%s} :aliases {:cider/nrepl {:main-opts [%s]}}}' -M%s:cider/nrepl" 760 (if global-options (format "%s " global-options) "") 761 (string-join all-deps " ") 762 main-opts 763 (if cider-clojure-cli-aliases 764 ;; remove exec-opts flags -A -M -T or -X from cider-clojure-cli-aliases 765 ;; concatenated with :cider/nrepl to ensure :cider/nrepl comes last 766 (format "%s" (replace-regexp-in-string "^-\\(A\\|M\\|T\\|X\\)" "" cider-clojure-cli-aliases)) 767 "")))) 768 769 (defun cider-shadow-cljs-jack-in-dependencies (global-opts params dependencies) 770 "Create shadow-cljs jack-in deps. 771 Does so by concatenating GLOBAL-OPTS, DEPENDENCIES finally PARAMS." 772 (let ((dependencies (append (cider--jack-in-required-dependencies) dependencies))) 773 (concat 774 global-opts 775 (unless (seq-empty-p global-opts) " ") 776 (mapconcat #'identity 777 (seq-map (lambda (dep) (format "-d %s:%s" (car dep) (cadr dep))) dependencies) 778 " ") 779 " " 780 params))) 781 782 (defun cider-add-clojure-dependencies-maybe (dependencies) 783 "Return DEPENDENCIES with an added Clojure dependency if requested. 784 See also `cider-jack-in-auto-inject-clojure'." 785 (if cider-jack-in-auto-inject-clojure 786 (if (consp cider-jack-in-auto-inject-clojure) 787 (cons cider-jack-in-auto-inject-clojure dependencies) 788 (cons (list cider-clojure-artifact-id 789 (cond 790 ((stringp cider-jack-in-auto-inject-clojure) 791 cider-jack-in-auto-inject-clojure) 792 ((eq cider-jack-in-auto-inject-clojure 'minimal) 793 cider-minimum-clojure-version) 794 ((eq cider-jack-in-auto-inject-clojure 'latest) 795 cider-latest-clojure-version))) 796 dependencies)) 797 dependencies)) 798 799 (defun cider-inject-jack-in-dependencies (global-opts params project-type) 800 "Return GLOBAL-OPTS and PARAMS with injected REPL dependencies. 801 These are set in `cider-jack-in-dependencies', `cider-jack-in-lein-plugins' and 802 `cider-jack-in-nrepl-middlewares' are injected from the CLI according to 803 the used PROJECT-TYPE. Eliminates the need for hacking profiles.clj or the 804 boot script for supporting CIDER with its nREPL middleware and 805 dependencies." 806 (pcase project-type 807 ('lein (cider-lein-jack-in-dependencies 808 global-opts 809 params 810 (cider-add-clojure-dependencies-maybe 811 (append `(("nrepl/nrepl" ,cider-injected-nrepl-version)) cider-jack-in-dependencies)) 812 cider-jack-in-dependencies-exclusions 813 (cider-jack-in-normalized-lein-plugins) 814 (if cider-enrich-classpath 815 (append cider-jack-in-lein-middlewares 816 '("cider.enrich-classpath/middleware")) 817 cider-jack-in-lein-middlewares))) 818 ('boot (cider-boot-jack-in-dependencies 819 global-opts 820 params 821 (cider-add-clojure-dependencies-maybe 822 cider-jack-in-dependencies) 823 (cider-jack-in-normalized-nrepl-middlewares))) 824 ('clojure-cli (cider-clojure-cli-jack-in-dependencies 825 global-opts 826 params 827 (cider-add-clojure-dependencies-maybe 828 cider-jack-in-dependencies))) 829 ('babashka (concat 830 global-opts 831 (unless (seq-empty-p global-opts) " ") 832 params)) 833 ('shadow-cljs (cider-shadow-cljs-jack-in-dependencies 834 global-opts 835 params 836 (cider-add-clojure-dependencies-maybe 837 cider-jack-in-dependencies))) 838 ('gradle (cider-gradle-jack-in-dependencies 839 global-opts 840 params 841 (cider-add-clojure-dependencies-maybe 842 cider-jack-in-dependencies) 843 (cider-jack-in-normalized-nrepl-middlewares))) 844 ('nbb (concat 845 global-opts 846 (unless (seq-empty-p global-opts) " ") 847 params)) 848 (_ (error "Unsupported project type `%S'" project-type)))) 849 850 851 ;;; ClojureScript REPL creation 852 853 (defcustom cider-check-cljs-repl-requirements t 854 "When non-nil will run the requirement checks for the different cljs repls. 855 Generally you should not disable this unless you run into some faulty check." 856 :type 'boolean 857 :safe #'booleanp 858 :package-version '(cider . "0.17.0")) 859 860 (defun cider-clojurescript-present-p () 861 "Return non nil when ClojureScript is present." 862 (or 863 ;; This is nil for example for nbb. 864 (cider-library-present-p "cljs.core") 865 ;; demunge is not defined currently for normal cljs repls. 866 ;; So we end up making the two checks 867 (nrepl-dict-get (cider-sync-tooling-eval "cljs.core/demunge") "value"))) 868 869 (defun cider-verify-clojurescript-is-present () 870 "Check whether ClojureScript is present." 871 (unless (cider-clojurescript-present-p) 872 (user-error "ClojureScript is not available. See https://docs.cider.mx/cider/basics/clojurescript for details"))) 873 874 (defun cider-verify-piggieback-is-present () 875 "Check whether the piggieback middleware is present." 876 (unless (cider-library-present-p "cider.piggieback") 877 (user-error "Piggieback 0.4.x (aka cider/piggieback) is not available. See https://docs.cider.mx/cider/basics/clojurescript for details"))) 878 879 (defun cider-check-node-requirements () 880 "Check whether we can start a Node ClojureScript REPL." 881 (cider-verify-piggieback-is-present) 882 (unless (executable-find "node") 883 (user-error "Node.js is not present on the exec-path. Make sure you've installed it and your exec-path is properly set"))) 884 885 (defun cider-check-figwheel-requirements () 886 "Check whether we can start a Figwheel ClojureScript REPL." 887 (cider-verify-piggieback-is-present) 888 (unless (cider-library-present-p "figwheel-sidecar.repl") 889 (user-error "Figwheel-sidecar is not available. Please check https://docs.cider.mx/cider/basics/clojurescript for details"))) 890 891 (defun cider-check-figwheel-main-requirements () 892 "Check whether we can start a Figwheel ClojureScript REPL." 893 (cider-verify-piggieback-is-present) 894 (unless (cider-library-present-p "figwheel.main") 895 (user-error "Figwheel-main is not available. Please check https://docs.cider.mx/cider/basics/clojurescript for details"))) 896 897 (defun cider-check-weasel-requirements () 898 "Check whether we can start a Weasel ClojureScript REPL." 899 (cider-verify-piggieback-is-present) 900 (unless (cider-library-present-p "weasel.repl.server") 901 (user-error "Weasel in not available. Please check https://docs.cider.mx/cider/basics/clojurescript/#browser-connected-clojurescript-repl for details"))) 902 903 (defun cider-check-boot-requirements () 904 "Check whether we can start a Boot ClojureScript REPL." 905 (cider-verify-piggieback-is-present) 906 (unless (cider-library-present-p "adzerk.boot-cljs-repl") 907 (user-error "The Boot ClojureScript REPL is not available. Please check https://github.com/adzerk-oss/boot-cljs-repl/blob/master/README.md for details"))) 908 909 (defun cider-check-krell-requirements () 910 "Check whether we can start a Krell ClojureScript REPL." 911 (cider-verify-piggieback-is-present) 912 (unless (cider-library-present-p "krell.repl") 913 (user-error "The Krell ClojureScript REPL is not available. Please check https://github.com/vouch-opensource/krell for details"))) 914 915 (defun cider-check-shadow-cljs-requirements () 916 "Check whether we can start a shadow-cljs REPL." 917 (unless (cider-library-present-p "shadow.cljs.devtools.api") 918 (user-error "The shadow-cljs ClojureScript REPL is not available. Please check https://docs.cider.mx/cider/basics/clojurescript for details"))) 919 920 (defun cider-normalize-cljs-init-options (options) 921 "Normalize the OPTIONS string used for initializing a ClojureScript REPL." 922 (if (or (string-prefix-p "{" options) 923 (string-prefix-p "(" options) 924 (string-prefix-p "[" options) 925 (string-prefix-p ":" options) 926 (string-prefix-p "\"" options)) 927 options 928 (concat ":" options))) 929 930 (defcustom cider-shadow-watched-builds nil 931 "Defines the list of builds `shadow-cljs' should watch." 932 :type '(repeat string) 933 :safe #'listp 934 :package-version '(cider . "1.0")) 935 936 (defcustom cider-shadow-default-options nil 937 "Defines default `shadow-cljs' options." 938 :type 'string 939 :safe (lambda (s) (or (null s) (stringp s))) 940 :package-version '(cider . "0.18.0")) 941 942 (defun cider--shadow-parse-builds (hash) 943 "Parses the build names of a shadow-cljs.edn HASH map. 944 The default options of `browser-repl' and `node-repl' are also included." 945 (let* ((builds (when (hash-table-p hash) 946 (gethash :builds hash))) 947 (build-keys (when (hash-table-p builds) 948 (hash-table-keys builds)))) 949 (append build-keys '(browser-repl node-repl)))) 950 951 (defun cider--shadow-get-builds () 952 "Extract build names from the shadow-cljs.edn config file in the project root." 953 (let ((shadow-edn (concat (clojure-project-dir) "shadow-cljs.edn"))) 954 (when (file-exists-p shadow-edn) 955 (with-temp-buffer 956 (insert-file-contents shadow-edn) 957 (let ((hash (car (parseedn-read '((shadow/env . identity)))))) 958 (cider--shadow-parse-builds hash)))))) 959 960 (defun cider-shadow-select-cljs-init-form () 961 "Generate the init form for a shadow-cljs select-only REPL. 962 We have to prompt the user to select a build, that's why this is a command, 963 not just a string." 964 (let ((form "(do (require '[shadow.cljs.devtools.api :as shadow]) (shadow/nrepl-select %s))") 965 (options (or cider-shadow-default-options 966 (completing-read "Select shadow-cljs build: " 967 (cider--shadow-get-builds))))) 968 (format form (cider-normalize-cljs-init-options options)))) 969 970 (defun cider-shadow-cljs-init-form () 971 "Generate the init form for a shadow-cljs REPL. 972 We have to prompt the user to select a build, that's why 973 this is a command, not just a string." 974 (let* ((shadow-require "(require '[shadow.cljs.devtools.api :as shadow])") 975 976 (default-build (cider-normalize-cljs-init-options 977 (or cider-shadow-default-options 978 (car cider-shadow-watched-builds) 979 (completing-read "Select shadow-cljs build: " 980 (cider--shadow-get-builds))))) 981 982 (watched-builds (or (mapcar #'cider-normalize-cljs-init-options cider-shadow-watched-builds) 983 (list default-build))) 984 985 (watched-builds-form (mapconcat (lambda (build) (format "(shadow/watch %s)" build)) 986 watched-builds 987 " ")) 988 ;; form used for user-defined builds 989 (user-build-form "(do %s %s (shadow/nrepl-select %s))") 990 ;; form used for built-in builds like :browser-repl and :node-repl 991 (default-build-form "(do %s (shadow/%s))")) 992 (if (member default-build '(":browser-repl" ":node-repl")) 993 (format default-build-form shadow-require (string-remove-prefix ":" default-build)) 994 (format user-build-form shadow-require watched-builds-form default-build)))) 995 996 (defcustom cider-figwheel-main-default-options nil 997 "Defines the `figwheel.main/start' options. 998 999 Note that figwheel-main/start can also accept a map of options, refer to 1000 Figwheel for details." 1001 :type 'string 1002 :safe (lambda (s) (or (null s) (stringp s))) 1003 :package-version '(cider . "0.18.0")) 1004 1005 (defun cider--figwheel-main-get-builds () 1006 "Extract build names from the <build-id>.cljs.edn config files. 1007 Fetches them in the project root." 1008 (when-let ((project-dir (clojure-project-dir))) 1009 (let ((builds (directory-files project-dir nil ".*\\.cljs\\.edn"))) 1010 (mapcar (lambda (f) (string-match "^\\(.*\\)\\.cljs\\.edn" f) 1011 (match-string 1 f)) 1012 builds)))) 1013 1014 (defun cider-figwheel-main-init-form () 1015 "Produce the figwheel-main ClojureScript init form." 1016 (let ((form "(do (require 'figwheel.main) (figwheel.main/start %s))") 1017 (builds (cider--figwheel-main-get-builds))) 1018 (cond 1019 (cider-figwheel-main-default-options 1020 (format form (cider-normalize-cljs-init-options (string-trim cider-figwheel-main-default-options)))) 1021 1022 (builds 1023 (format form (cider-normalize-cljs-init-options (completing-read "Select figwheel-main build: " builds)))) 1024 1025 (t (user-error "No figwheel-main build files (<build-id>.cljs.edn) were found"))))) 1026 1027 (defcustom cider-custom-cljs-repl-init-form nil 1028 "The form used to start a custom ClojureScript REPL. 1029 When set it becomes the return value of the `cider-custom-cljs-repl-init-form' 1030 function, which normally prompts for the init form. 1031 1032 This defcustom is mostly intended for use with .dir-locals.el for 1033 cases where it doesn't make sense to register a new ClojureScript REPL type." 1034 :type 'string 1035 :safe (lambda (s) (or (null s) (stringp s))) 1036 :package-version '(cider . "0.23.0")) 1037 1038 (defun cider-custom-cljs-repl-init-form () 1039 "The form used to start a custom ClojureScript REPL. 1040 Defaults to the value of `cider-custom-cljs-repl-init-form'. 1041 If it's nil the function will prompt for a form. 1042 The supplied string will be wrapped in a do form if needed." 1043 (or 1044 cider-custom-cljs-repl-init-form 1045 (let ((form (read-from-minibuffer "Please, provide a form to start a ClojureScript REPL: "))) 1046 ;; TODO: We should probably make this more robust (e.g. by using a regexp or 1047 ;; parsing the form). 1048 (if (string-prefix-p "(do" form) 1049 form 1050 (format "(do %s)" form))))) 1051 1052 (defvar cider-cljs-repl-types 1053 '((figwheel "(do (require 'figwheel-sidecar.repl-api) (figwheel-sidecar.repl-api/start-figwheel!) (figwheel-sidecar.repl-api/cljs-repl))" 1054 cider-check-figwheel-requirements) 1055 (figwheel-main cider-figwheel-main-init-form cider-check-figwheel-main-requirements) 1056 (figwheel-connected "(figwheel-sidecar.repl-api/cljs-repl)" 1057 cider-check-figwheel-requirements) 1058 (browser "(do (require 'cljs.repl.browser) (cider.piggieback/cljs-repl (cljs.repl.browser/repl-env)))") 1059 (node "(do (require 'cljs.repl.node) (cider.piggieback/cljs-repl (cljs.repl.node/repl-env)))" 1060 cider-check-node-requirements) 1061 (weasel "(do (require 'weasel.repl.websocket) (cider.piggieback/cljs-repl (weasel.repl.websocket/repl-env :ip \"127.0.0.1\" :port 9001)))" 1062 cider-check-weasel-requirements) 1063 (boot "(do (require 'adzerk.boot-cljs-repl) (adzerk.boot-cljs-repl/start-repl))" 1064 cider-check-boot-requirements) 1065 (shadow cider-shadow-cljs-init-form cider-check-shadow-cljs-requirements) 1066 (shadow-select cider-shadow-select-cljs-init-form cider-check-shadow-cljs-requirements) 1067 (krell "(require '[clojure.edn :as edn] 1068 '[clojure.java.io :as io] 1069 '[cider.piggieback] 1070 '[krell.api :as krell] 1071 '[krell.repl]) 1072 (def config (edn/read-string (slurp (io/file \"build.edn\")))) 1073 (apply cider.piggieback/cljs-repl (krell.repl/repl-env) (mapcat identity config))" 1074 cider-check-krell-requirements) 1075 ;; native cljs repl, no form required. 1076 (nbb) 1077 (custom cider-custom-cljs-repl-init-form nil)) 1078 "A list of supported ClojureScript REPLs. 1079 1080 For each one we have its name, and then, if the repl is not a native 1081 ClojureScript REPL, the form we need to evaluate in a Clojure REPL to 1082 switch to the ClojureScript REPL and functions to verify their 1083 requirements. 1084 1085 The form, if any, should be either a string or a function producing a 1086 string.") 1087 1088 (defun cider-register-cljs-repl-type (type &optional init-form requirements-fn) 1089 "Register a new ClojureScript REPL type. 1090 1091 Types are defined by the following: 1092 1093 - TYPE - symbol identifier that will be used to refer to the REPL type 1094 - INIT-FORM - (optional) string or function (symbol) producing string 1095 - REQUIREMENTS-FN - function to check whether the REPL can be started. 1096 This param is optional. 1097 1098 All this function does is modifying `cider-cljs-repl-types'. 1099 It's intended to be used in your Emacs config." 1100 (unless (symbolp type) 1101 (user-error "The REPL type must be a symbol")) 1102 (unless (or (null init-form) (stringp init-form) (symbolp init-form)) 1103 (user-error "The init form must be a string or a symbol referring to a function or nil")) 1104 (unless (or (null requirements-fn) (symbolp requirements-fn)) 1105 (user-error "The requirements-fn must be a symbol referring to a function")) 1106 (add-to-list 'cider-cljs-repl-types (list type init-form requirements-fn))) 1107 1108 (defcustom cider-default-cljs-repl nil 1109 "The default ClojureScript REPL to start. 1110 This affects commands like `cider-jack-in-cljs'. Generally it's 1111 intended to be set via .dir-locals.el for individual projects, as its 1112 relatively unlikely you'd like to use the same type of REPL in each project 1113 you're working on." 1114 :type '(choice (const :tag "Figwheel" figwheel) 1115 (const :tag "Figwheel Main" figwheel-main) 1116 (const :tag "Browser" browser) 1117 (const :tag "Node" node) 1118 (const :tag "Weasel" weasel) 1119 (const :tag "Boot" boot) 1120 (const :tag "Shadow" shadow) 1121 (const :tag "Shadow w/o Server" shadow-select) 1122 (const :tag "Krell" krell) 1123 (const :tag "Nbb" nbb) 1124 (const :tag "Custom" custom)) 1125 :safe #'symbolp 1126 :package-version '(cider . "0.17.0")) 1127 1128 (make-obsolete-variable 'cider-cljs-lein-repl 'cider-default-cljs-repl "0.17") 1129 (make-obsolete-variable 'cider-cljs-boot-repl 'cider-default-cljs-repl "0.17") 1130 (make-obsolete-variable 'cider-cljs-gradle-repl 'cider-default-cljs-repl "0.17") 1131 1132 (defvar cider--select-cljs-repl-history nil) 1133 (defun cider-select-cljs-repl (&optional default) 1134 "Select the ClojureScript REPL to use with `cider-jack-in-cljs'. 1135 DEFAULT is the default ClojureScript REPL to offer in completion." 1136 (let ((repl-types (mapcar #'car cider-cljs-repl-types))) 1137 (intern (completing-read "Select ClojureScript REPL type: " repl-types 1138 nil nil nil 'cider--select-cljs-repl-history 1139 (or default (car cider--select-cljs-repl-history)))))) 1140 1141 (defun cider-cljs-repl-form (repl-type) 1142 "Get the cljs REPL form for REPL-TYPE, if any." 1143 (if-let* ((repl-type-info (seq-find 1144 (lambda (entry) 1145 (eq (car entry) repl-type)) 1146 cider-cljs-repl-types))) 1147 (when-let ((repl-form (cadr repl-type-info))) 1148 ;; repl-form can be either a string or a function producing a string 1149 (if (symbolp repl-form) 1150 (funcall repl-form) 1151 repl-form)) 1152 (user-error "No ClojureScript REPL type %s found. Please make sure that `cider-cljs-repl-types' has an entry for it" repl-type))) 1153 1154 (defun cider-verify-cljs-repl-requirements (&optional repl-type) 1155 "Verify that the requirements for REPL-TYPE are met. 1156 Return REPL-TYPE if requirements are met." 1157 (let ((repl-type (or repl-type 1158 cider-default-cljs-repl 1159 (cider-select-cljs-repl)))) 1160 (when cider-check-cljs-repl-requirements 1161 (when-let* ((fun (nth 2 (seq-find 1162 (lambda (entry) 1163 (eq (car entry) repl-type)) 1164 cider-cljs-repl-types)))) 1165 (funcall fun))) 1166 repl-type)) 1167 1168 (defun cider--check-cljs (&optional cljs-type no-error) 1169 "Verify that all cljs requirements are met for CLJS-TYPE connection. 1170 Return REPL-TYPE of requirement are met, and throw an ‘user-error’ otherwise. 1171 When NO-ERROR is non-nil, don't throw an error, issue a message and return 1172 nil." 1173 (if no-error 1174 (condition-case ex 1175 (progn 1176 (cider-verify-clojurescript-is-present) 1177 (cider-verify-cljs-repl-requirements cljs-type)) 1178 (error 1179 (message "Invalid ClojureScript dependency: %S" ex) 1180 nil)) 1181 (cider-verify-clojurescript-is-present) 1182 (cider-verify-cljs-repl-requirements cljs-type))) 1183 1184 (defun cider--offer-to-open-app-in-browser (server-buffer) 1185 "Look for a server address in SERVER-BUFFER and offer to open it." 1186 (when (buffer-live-p server-buffer) 1187 (with-current-buffer server-buffer 1188 (save-excursion 1189 (goto-char (point-min)) 1190 (when-let* ((url (and (search-forward-regexp "http://localhost:[0-9]+" nil 'noerror) 1191 (match-string 0)))) 1192 (when (y-or-n-p (format "Visit ‘%s’ in a browser? " url)) 1193 (browse-url url))))))) 1194 1195 1196 ;;; User Level Connectors 1197 1198 ;;;###autoload (autoload 'cider-start-map "cider" "CIDER jack-in and connect keymap." t 'keymap) 1199 (defvar cider-start-map 1200 (let ((map (define-prefix-command 'cider-start-map))) 1201 (define-key map (kbd "x") #'cider) 1202 (define-key map (kbd "C-x") #'cider) 1203 (define-key map (kbd "j j") #'cider-jack-in-clj) 1204 (define-key map (kbd "j s") #'cider-jack-in-cljs) 1205 (define-key map (kbd "j m") #'cider-jack-in-clj&cljs) 1206 (define-key map (kbd "j u") #'cider-jack-in-universal) 1207 (define-key map (kbd "C-j j") #'cider-jack-in-clj) 1208 (define-key map (kbd "C-j s") #'cider-jack-in-cljs) 1209 (define-key map (kbd "C-j m") #'cider-jack-in-clj&cljs) 1210 (define-key map (kbd "C-j C-j") #'cider-jack-in-clj) 1211 (define-key map (kbd "C-j C-s") #'cider-jack-in-cljs) 1212 (define-key map (kbd "C-j C-m") #'cider-jack-in-clj&cljs) 1213 (define-key map (kbd "c j") #'cider-connect-clj) 1214 (define-key map (kbd "c s") #'cider-connect-cljs) 1215 (define-key map (kbd "c m") #'cider-connect-clj&cljs) 1216 (define-key map (kbd "C-c j") #'cider-connect-clj) 1217 (define-key map (kbd "C-c s") #'cider-connect-cljs) 1218 (define-key map (kbd "C-c m") #'cider-connect-clj&cljs) 1219 (define-key map (kbd "C-c C-j") #'cider-connect-clj) 1220 (define-key map (kbd "C-c C-s") #'cider-connect-cljs) 1221 (define-key map (kbd "C-c C-m") #'cider-connect-clj&cljs) 1222 (define-key map (kbd "s j") #'cider-connect-sibling-clj) 1223 (define-key map (kbd "s s") #'cider-connect-sibling-cljs) 1224 (define-key map (kbd "C-s j") #'cider-connect-sibling-clj) 1225 (define-key map (kbd "C-s s") #'cider-connect-sibling-cljs) 1226 (define-key map (kbd "C-s C-j") #'cider-connect-sibling-clj) 1227 (define-key map (kbd "C-s C-s") #'cider-connect-sibling-cljs) 1228 map) 1229 "CIDER jack-in and connect keymap.") 1230 1231 ;;;###autoload 1232 (defun cider-jack-in-clj (params) 1233 "Start an nREPL server for the current project and connect to it. 1234 PARAMS is a plist optionally containing :project-dir and :jack-in-cmd. 1235 With the prefix argument, allow editing of the jack in command; with a 1236 double prefix prompt for all these parameters." 1237 (interactive "P") 1238 (let ((params (thread-first 1239 params 1240 (cider--update-project-dir) 1241 (cider--check-existing-session) 1242 (cider--update-jack-in-cmd)))) 1243 (nrepl-start-server-process 1244 (plist-get params :project-dir) 1245 (plist-get params :jack-in-cmd) 1246 (lambda (server-buffer) 1247 (cider-connect-sibling-clj params server-buffer))))) 1248 1249 ;;;###autoload 1250 (defun cider-jack-in-cljs (params) 1251 "Start an nREPL server for the current project and connect to it. 1252 PARAMS is a plist optionally containing :project-dir, :jack-in-cmd and 1253 :cljs-repl-type (e.g. Node, Figwheel, etc). With the prefix argument, 1254 allow editing of the jack in command; with a double prefix prompt for all 1255 these parameters." 1256 (interactive "P") 1257 (let ((cider-jack-in-dependencies (append cider-jack-in-dependencies cider-jack-in-cljs-dependencies)) 1258 (cider-jack-in-lein-plugins (append cider-jack-in-lein-plugins cider-jack-in-cljs-lein-plugins)) 1259 (cider-jack-in-nrepl-middlewares (append cider-jack-in-nrepl-middlewares cider-jack-in-cljs-nrepl-middlewares)) 1260 (orig-buffer (current-buffer))) 1261 ;; cider--update-jack-in-cmd relies indirectly on the above dynamic vars 1262 (let ((params (thread-first 1263 params 1264 (cider--update-project-dir) 1265 (cider--check-existing-session) 1266 (cider--update-jack-in-cmd)))) 1267 (nrepl-start-server-process 1268 (plist-get params :project-dir) 1269 (plist-get params :jack-in-cmd) 1270 (lambda (server-buffer) 1271 (with-current-buffer orig-buffer 1272 (cider-connect-sibling-cljs params server-buffer))))))) 1273 1274 ;;;###autoload 1275 (defun cider-jack-in-clj&cljs (&optional params soft-cljs-start) 1276 "Start an nREPL server and connect with clj and cljs REPLs. 1277 PARAMS is a plist optionally containing :project-dir, :jack-in-cmd and 1278 :cljs-repl-type (e.g. Node, Figwheel, etc). With the prefix argument, 1279 allow for editing of the jack in command; with a double prefix prompt for 1280 all these parameters. When SOFT-CLJS-START is non-nil, start cljs REPL 1281 only when the ClojureScript dependencies are met." 1282 (interactive "P") 1283 (let ((cider-jack-in-dependencies (append cider-jack-in-dependencies cider-jack-in-cljs-dependencies)) 1284 (cider-jack-in-lein-plugins (append cider-jack-in-lein-plugins cider-jack-in-cljs-lein-plugins)) 1285 (cider-jack-in-nrepl-middlewares (append cider-jack-in-nrepl-middlewares cider-jack-in-cljs-nrepl-middlewares)) 1286 (orig-buffer (current-buffer))) 1287 ;; cider--update-jack-in-cmd relies indirectly on the above dynamic vars 1288 (let ((params (thread-first 1289 params 1290 (cider--update-project-dir) 1291 (cider--check-existing-session) 1292 (cider--update-jack-in-cmd) 1293 (cider--update-cljs-type) 1294 ;; already asked, don't ask on sibling connect 1295 (plist-put :do-prompt nil)))) 1296 (nrepl-start-server-process 1297 (plist-get params :project-dir) 1298 (plist-get params :jack-in-cmd) 1299 (lambda (server-buffer) 1300 (with-current-buffer orig-buffer 1301 (let ((clj-repl (cider-connect-sibling-clj params server-buffer))) 1302 (if soft-cljs-start 1303 (when (cider--check-cljs (plist-get params :cljs-repl-type) 'no-error) 1304 (cider-connect-sibling-cljs params clj-repl)) 1305 (cider-connect-sibling-cljs params clj-repl))))))))) 1306 1307 ;;;###autoload 1308 (defun cider-connect-sibling-clj (params &optional other-repl) 1309 "Create a Clojure REPL with the same server as OTHER-REPL. 1310 PARAMS is for consistency with other connection commands and is currently 1311 ignored. OTHER-REPL defaults to `cider-current-repl' and in programs can 1312 also be a server buffer, in which case a new session with a REPL for that 1313 server is created." 1314 (interactive "P") 1315 (cider-nrepl-connect 1316 (let* ((other-repl (or other-repl (cider-current-repl 'any 'ensure))) 1317 (other-params (cider--gather-connect-params nil other-repl)) 1318 (ses-name (unless (nrepl-server-p other-repl) 1319 (sesman-session-name-for-object 'CIDER other-repl)))) 1320 (thread-first 1321 params 1322 (cider--update-do-prompt) 1323 (append other-params) 1324 (plist-put :repl-init-function nil) 1325 (plist-put :repl-type 'clj) 1326 (plist-put :session-name ses-name))))) 1327 1328 ;;;###autoload 1329 (defun cider-connect-sibling-cljs (params &optional other-repl) 1330 "Create a ClojureScript REPL with the same server as OTHER-REPL. 1331 PARAMS is a plist optionally containing :cljs-repl-type (e.g. Node, 1332 Figwheel, etc). All other parameters are inferred from the OTHER-REPL. 1333 OTHER-REPL defaults to `cider-current-repl' but in programs can also be a 1334 server buffer, in which case a new session for that server is created." 1335 (interactive "P") 1336 (let* ((other-repl (or other-repl (cider-current-repl 'any 'ensure))) 1337 (other-params (cider--gather-connect-params nil other-repl)) 1338 (ses-name (unless (nrepl-server-p other-repl) 1339 (sesman-session-name-for-object 'CIDER other-repl)))) 1340 (cider-nrepl-connect 1341 (thread-first 1342 params 1343 (cider--update-do-prompt) 1344 (append other-params) 1345 (cider--update-cljs-type) 1346 (cider--update-cljs-init-function) 1347 (plist-put :session-name ses-name) 1348 (plist-put :repl-type 'cljs))))) 1349 1350 ;;;###autoload 1351 (defun cider-connect-clj (&optional params) 1352 "Initialize a Clojure connection to an nREPL server. 1353 PARAMS is a plist optionally containing :host, :port and :project-dir. On 1354 prefix argument, prompt for all the parameters." 1355 (interactive "P") 1356 (cider-nrepl-connect 1357 (thread-first 1358 params 1359 (cider--update-project-dir) 1360 (cider--update-host-port) 1361 (cider--check-existing-session) 1362 (plist-put :repl-init-function nil) 1363 (plist-put :session-name nil) 1364 (plist-put :repl-type 'clj)))) 1365 1366 ;;;###autoload 1367 (defun cider-connect-cljs (&optional params) 1368 "Initialize a ClojureScript connection to an nREPL server. 1369 PARAMS is a plist optionally containing :host, :port, :project-dir and 1370 :cljs-repl-type (e.g. Node, Figwheel, etc). On prefix, prompt for all the 1371 parameters regardless of their supplied or default values." 1372 (interactive "P") 1373 (cider-nrepl-connect 1374 (thread-first 1375 params 1376 (cider--update-project-dir) 1377 (cider--update-host-port) 1378 (cider--check-existing-session) 1379 (cider--update-cljs-type) 1380 (cider--update-cljs-init-function) 1381 (plist-put :session-name nil) 1382 (plist-put :repl-type 'cljs)))) 1383 1384 ;;;###autoload 1385 (defun cider-connect-clj&cljs (params &optional soft-cljs-start) 1386 "Initialize a Clojure and ClojureScript connection to an nREPL server. 1387 PARAMS is a plist optionally containing :host, :port, :project-dir and 1388 :cljs-repl-type (e.g. Node, Figwheel, etc). When SOFT-CLJS-START is 1389 non-nil, don't start if ClojureScript requirements are not met." 1390 (interactive "P") 1391 (let* ((params (thread-first 1392 params 1393 (cider--update-project-dir) 1394 (cider--update-host-port) 1395 (cider--check-existing-session) 1396 (cider--update-cljs-type))) 1397 (clj-repl (cider-connect-clj params))) 1398 (if soft-cljs-start 1399 (when (cider--check-cljs (plist-get params :cljs-repl-type) 'no-error) 1400 (cider-connect-sibling-cljs params clj-repl)) 1401 (cider-connect-sibling-cljs params clj-repl)))) 1402 1403 (defvar cider-connection-init-commands 1404 '(cider-jack-in-clj 1405 cider-jack-in-cljs 1406 cider-jack-in-clj&cljs 1407 cider-connect-clj 1408 cider-connect-cljs 1409 cider-connect-clj&cljs 1410 cider-connect-sibling-clj 1411 cider-connect-sibling-cljs) 1412 "A list of all user-level connection init commands in CIDER.") 1413 1414 ;;;###autoload 1415 (defun cider () 1416 "Start a connection of any type interactively." 1417 (interactive) 1418 (when-let* ((command (intern (completing-read "Select command: " cider-connection-init-commands)))) 1419 (call-interactively command))) 1420 1421 1422 ;;; PARAMS updating 1423 1424 (defun cider--update-do-prompt (params) 1425 "Update :do-prompt in PARAMS." 1426 (cond ((equal params '(4)) (list :edit-jack-in-command t)) 1427 ((equal params '(16)) (list :do-prompt t)) 1428 (t params))) 1429 1430 (defun cider--update-project-dir (params) 1431 "Update :project-dir in PARAMS. 1432 1433 Params is a plist with the following keys (non-exhaustive) 1434 1435 :edit-project-dir prompt (optional) ask user to confirm the project root 1436 directory." 1437 (let* ((params (cider--update-do-prompt params)) 1438 (proj-dir (if (or (plist-get params :do-prompt) 1439 (plist-get params :edit-project-dir)) 1440 (read-directory-name "Project: " 1441 (clojure-project-dir (cider-current-dir))) 1442 (plist-get params :project-dir))) 1443 (orig-buffer (current-buffer))) 1444 (if (or (null proj-dir) 1445 (file-in-directory-p default-directory proj-dir)) 1446 (plist-put params :project-dir 1447 (or proj-dir 1448 (clojure-project-dir (cider-current-dir)))) 1449 ;; If proj-dir is not a parent of default-directory, transfer all local 1450 ;; variables and hack dir-local variables into a temporary buffer and keep 1451 ;; that buffer within `params` for the later use by other --update- 1452 ;; functions. The context buffer should not be used outside of the param 1453 ;; initialization pipeline. Therefore, we don't bother with making it 1454 ;; unique or killing it anywhere. 1455 (let ((context-buf-name " *cider-context-buffer*")) 1456 (when (get-buffer context-buf-name) 1457 (kill-buffer context-buf-name)) 1458 (with-current-buffer (get-buffer-create context-buf-name) 1459 (dolist (pair (buffer-local-variables orig-buffer)) 1460 (pcase pair 1461 (`(,name . ,value) ;ignore unbound variables 1462 (ignore-errors (set (make-local-variable name) value)))) 1463 (setq-local buffer-file-name nil)) 1464 (let ((default-directory proj-dir)) 1465 (hack-dir-local-variables-non-file-buffer) 1466 (thread-first 1467 params 1468 (plist-put :project-dir proj-dir) 1469 (plist-put :--context-buffer (current-buffer))))))))) 1470 1471 (defun cider--update-cljs-type (params) 1472 "Update :cljs-repl-type in PARAMS." 1473 (with-current-buffer (or (plist-get params :--context-buffer) 1474 (current-buffer)) 1475 (let ((params (cider--update-do-prompt params)) 1476 (inferred-type (or (plist-get params :cljs-repl-type) 1477 cider-default-cljs-repl))) 1478 (plist-put params :cljs-repl-type 1479 (if (plist-get params :do-prompt) 1480 (cider-select-cljs-repl inferred-type) 1481 (or inferred-type 1482 (cider-select-cljs-repl))))))) 1483 1484 (defcustom cider-edit-jack-in-command nil 1485 "When truthy allow the user to edit the command." 1486 :type 'boolean 1487 :safe #'booleanp 1488 :version '(cider . "0.22.0")) 1489 1490 (defvar cider--jack-in-nrepl-params-history nil 1491 "History list for user-specified jack-in nrepl command params.") 1492 1493 (defvar cider--jack-in-cmd-history nil 1494 "History list for user-specified jack-in commands.") 1495 1496 (defun cider--powershell-encode-command (cmd-params) 1497 "Base64 encode the powershell command and jack-in CMD-PARAMS for clojure-cli." 1498 (let* ((quoted-params (replace-regexp-in-string "\"" "\"\"" cmd-params)) 1499 (command (format "clojure %s" quoted-params)) 1500 (utf-16le-command (encode-coding-string command 'utf-16le))) 1501 (format "-encodedCommand %s" (base64-encode-string utf-16le-command t)))) 1502 1503 (defun cider--update-jack-in-cmd (params) 1504 "Update :jack-in-cmd key in PARAMS. 1505 1506 PARAMS is a plist with the following keys (non-exhaustive list) 1507 1508 :project-type optional, the project type to create the command for; see 1509 `cider-jack-in-command' for the list of valid types)." 1510 (cond 1511 ((plist-get params :jack-in-cmd) params) 1512 (cider-jack-in-cmd (plist-put params :jack-in-cmd cider-jack-in-cmd)) 1513 (t (let* ((params (cider--update-do-prompt params)) 1514 (project-dir (plist-get params :project-dir)) 1515 (params-project-type (plist-get params :project-type)) 1516 (project-type (or params-project-type 1517 (cider-project-type project-dir))) 1518 (command (cider-jack-in-command project-type)) 1519 (command-resolved (cider-jack-in-resolve-command project-type)) 1520 (command-global-opts (cider-jack-in-global-options project-type)) 1521 (command-params (cider-jack-in-params project-type))) 1522 (if command-resolved 1523 (with-current-buffer (or (plist-get params :--context-buffer) 1524 (current-buffer)) 1525 (let* ((command-params (if (plist-get params :do-prompt) 1526 (read-string "nREPL server command: " 1527 command-params 1528 'cider--jack-in-nrepl-params-history) 1529 command-params)) 1530 (cmd-params (if cider-inject-dependencies-at-jack-in 1531 (cider-inject-jack-in-dependencies command-global-opts command-params project-type) 1532 command-params))) 1533 (if (or project-dir cider-allow-jack-in-without-project) 1534 (when (or project-dir 1535 (eq cider-allow-jack-in-without-project t) 1536 (and (null project-dir) 1537 (eq cider-allow-jack-in-without-project 'warn) 1538 (or params-project-type 1539 (y-or-n-p "Are you sure you want to run `cider-jack-in' without a Clojure project? ")))) 1540 (let ((cmd (format "%s %s" command-resolved (if (or (string-equal command "powershell") 1541 (string-equal command "pwsh")) 1542 (cider--powershell-encode-command cmd-params) 1543 cmd-params)))) 1544 (plist-put params :jack-in-cmd (if (or cider-edit-jack-in-command 1545 (plist-get params :edit-jack-in-command)) 1546 (read-string "jack-in command: " cmd 'cider--jack-in-cmd-history) 1547 cmd)))) 1548 (user-error "`cider-jack-in' is not allowed without a Clojure project")))) 1549 (user-error "The %s executable isn't on your `exec-path'" command)))))) 1550 1551 (defun cider--update-host-port (params) 1552 "Update :host and :port; or :socket-file in PARAMS." 1553 (with-current-buffer (or (plist-get params :--context-buffer) 1554 (current-buffer)) 1555 (let* ((params (cider--update-do-prompt params)) 1556 (host (plist-get params :host)) 1557 (port (plist-get params :port)) 1558 (endpoint (if (plist-get params :do-prompt) 1559 (cider-select-endpoint) 1560 (if (and host port) 1561 (cons host port) 1562 (cider-select-endpoint))))) 1563 (if (equal "local-unix-domain-socket" (car endpoint)) 1564 (plist-put params :socket-file (cdr endpoint)) 1565 (thread-first 1566 params 1567 (plist-put :host (car endpoint)) 1568 (plist-put :port (cdr endpoint))))))) 1569 1570 (defun cider--update-cljs-init-function (params) 1571 "Update repl type and any init PARAMS for cljs connections. 1572 1573 The updated params are: 1574 1575 :cider-repl-cljs-upgrade-pending nil if it is a cljs REPL, or t 1576 when the init form is required to be sent to the REPL to switch 1577 over to cljs. 1578 1579 :repl-init-form The form that can switch the REPL over to cljs. 1580 1581 :repl-init-function The fn that switches the REPL over to cljs." 1582 (with-current-buffer (or (plist-get params :--context-buffer) 1583 (current-buffer)) 1584 (let* ((cljs-type (plist-get params :cljs-repl-type)) 1585 (repl-init-form (cider-cljs-repl-form cljs-type))) 1586 (if (null repl-init-form) 1587 (plist-put params :cider-repl-cljs-upgrade-pending nil) 1588 1589 (thread-first 1590 params 1591 (plist-put :cider-repl-cljs-upgrade-pending t) 1592 (plist-put :repl-init-function 1593 (lambda () 1594 (cider--check-cljs cljs-type) 1595 ;; FIXME: ideally this should be done in the state handler 1596 (setq-local cider-cljs-repl-type cljs-type) 1597 (cider-nrepl-send-request 1598 (list "op" "eval" 1599 "ns" (cider-current-ns) 1600 "code" repl-init-form) 1601 (cider-repl-handler (current-buffer))) 1602 (when (and (buffer-live-p nrepl-server-buffer) 1603 cider-offer-to-open-cljs-app-in-browser) 1604 (cider--offer-to-open-app-in-browser nrepl-server-buffer)))) 1605 (plist-put :repl-init-form repl-init-form)))))) 1606 1607 (defun cider--check-existing-session (params) 1608 "Ask for confirmation if a session with similar PARAMS already exists. 1609 If no session exists or user chose to proceed, return PARAMS. If the user 1610 canceled the action, signal quit." 1611 (let* ((proj-dir (plist-get params :project-dir)) 1612 (host (plist-get params :host)) 1613 (port (plist-get params :port)) 1614 (session (seq-find (lambda (ses) 1615 (let ((ses-params (cider--gather-session-params ses))) 1616 (and (equal proj-dir (plist-get ses-params :project-dir)) 1617 (or (null port) 1618 (equal port (plist-get ses-params :port))) 1619 (or (null host) 1620 (equal host (plist-get ses-params :host)))))) 1621 (sesman-current-sessions 'CIDER '(project))))) 1622 (when session 1623 (unless (y-or-n-p 1624 (concat 1625 "A CIDER session with the same connection parameters already exists (" (car session) "). " 1626 "Are you sure you want to create a new session instead of using `cider-connect-sibling-clj(s)'? ")) 1627 (let ((debug-on-quit nil)) 1628 (signal 'quit nil))))) 1629 params) 1630 1631 1632 ;;; Aliases 1633 1634 ;;;###autoload 1635 (defalias 'cider-jack-in #'cider-jack-in-clj) 1636 ;;;###autoload 1637 (defalias 'cider-connect #'cider-connect-clj) 1638 1639 1640 ;;; Helpers 1641 1642 (defun cider-current-host () 1643 "Retrieve the current host." 1644 (or (when (stringp buffer-file-name) 1645 (file-remote-p buffer-file-name 'host)) 1646 "localhost")) 1647 1648 (defun cider-select-endpoint () 1649 "Interactively select the host and port to connect to." 1650 (dolist (endpoint cider-known-endpoints) 1651 (unless (stringp (or (nth 2 endpoint) 1652 (nth 1 endpoint))) 1653 (user-error "The port for %s in `cider-known-endpoints' should be a string" 1654 (nth 0 endpoint)))) 1655 (let* ((ssh-hosts (cider--ssh-hosts)) 1656 (hosts (seq-uniq (append (when cider-host-history 1657 ;; history elements are strings of the form "host:port" 1658 (list (split-string (car cider-host-history) ":"))) 1659 (list (list (cider-current-host))) 1660 cider-known-endpoints 1661 ssh-hosts 1662 ;; always add localhost 1663 '(("localhost") 1664 ("local-unix-domain-socket"))))) 1665 (sel-host (cider--completing-read-host hosts)) 1666 (host (car sel-host)) 1667 (port (or (cadr sel-host) 1668 (if (equal host "local-unix-domain-socket") 1669 (cider--completing-read-socket-file) 1670 (cider--completing-read-port host (cider--infer-ports host ssh-hosts)))))) 1671 (cons host port))) 1672 1673 (defun cider--ssh-hosts () 1674 "Retrieve all ssh host from local configuration files." 1675 (seq-map (lambda (s) (list (replace-regexp-in-string ":$" "" s))) 1676 ;; `tramp-completion-mode' is obsoleted in 26 1677 (cl-progv (if (version< emacs-version "26") 1678 '(tramp-completion-mode) 1679 '(non-essential)) '(t) 1680 (tramp-completion-handle-file-name-all-completions "" "/ssh:")))) 1681 1682 (defun cider--completing-read-host (hosts) 1683 "Interactively select host from HOSTS. 1684 Each element in HOSTS is one of: (host), (host port) or (label host port). 1685 Return a list of the form (HOST PORT), where PORT can be nil." 1686 (let* ((hosts (cider-join-into-alist hosts)) 1687 (sel-host (completing-read "Host: " hosts nil nil nil 1688 'cider-host-history (caar hosts))) 1689 (host (or (cdr (assoc sel-host hosts)) (list sel-host)))) 1690 ;; remove the label 1691 (if (= 3 (length host)) (cdr host) host))) 1692 1693 (defun cider--tramp-file-name (vec) 1694 "A simple compatibility wrapper around `make-tramp-file-name'. 1695 Tramp version starting 26.1 is using a `cl-defstruct' rather than vanilla VEC." 1696 (if (version< emacs-version "26.1") 1697 vec 1698 (with-no-warnings 1699 (make-tramp-file-name :method (elt vec 0) 1700 :host (elt vec 2))))) 1701 1702 (defcustom cider-infer-remote-nrepl-ports nil 1703 "When true, cider will use ssh to try to infer nREPL ports on remote hosts." 1704 :type 'boolean 1705 :safe #'booleanp 1706 :package-version '(cider . "0.19.0")) 1707 1708 (defun cider--infer-ports (host ssh-hosts) 1709 "Infer nREPL ports on HOST. 1710 Return a list of elements of the form (directory port). SSH-HOSTS is a list 1711 of remote SSH hosts." 1712 (let ((localp (or (nrepl-local-host-p host) 1713 (not (assoc-string host ssh-hosts))))) 1714 (if localp 1715 ;; change dir: current file might be remote 1716 (let* ((change-dir-p (file-remote-p default-directory)) 1717 (default-directory (if change-dir-p "~/" default-directory))) 1718 (cider-locate-running-nrepl-ports (unless change-dir-p default-directory))) 1719 (when cider-infer-remote-nrepl-ports 1720 (let ((vec (vector "sshx" nil host "" nil)) 1721 ;; change dir: user might want to connect to a different remote 1722 (dir (when (file-remote-p default-directory) 1723 (with-parsed-tramp-file-name default-directory cur 1724 (when (string= cur-host host) default-directory))))) 1725 (tramp-maybe-open-connection (cider--tramp-file-name vec)) 1726 (with-current-buffer (tramp-get-connection-buffer (cider--tramp-file-name vec)) 1727 (cider-locate-running-nrepl-ports dir))))))) 1728 1729 (defun cider--completing-read-port (host ports) 1730 "Interactively select port for HOST from PORTS." 1731 (let* ((ports (cider-join-into-alist ports)) 1732 (sel-port (completing-read (format "Port for %s: " host) ports 1733 nil nil nil nil (caar ports))) 1734 (port (or (cdr (assoc sel-port ports)) sel-port)) 1735 (port (if (listp port) (cadr port) port))) 1736 (if (stringp port) (string-to-number port) port))) 1737 1738 (defun cider--completing-read-socket-file () 1739 "Interactively select unix domain socket file name." 1740 (read-file-name "Socket File: " nil nil t nil 1741 (lambda (filename) 1742 "Predicate: auto-complete only socket-files and directories" 1743 (let ((filetype (string-to-char 1744 (file-attribute-modes 1745 (file-attributes 1746 filename))))) 1747 (or (eq ?s filetype) 1748 (eq ?d filetype)))))) 1749 1750 (defun cider-locate-running-nrepl-ports (&optional dir) 1751 "Locate ports of running nREPL servers. 1752 When DIR is non-nil also look for nREPL port files in DIR. Return a list 1753 of list of the form (project-dir port)." 1754 (let* ((paths (cider--running-nrepl-paths)) 1755 (proj-ports (apply #'append 1756 (mapcar (lambda (d) 1757 (mapcar (lambda (p) (list (file-name-nondirectory (directory-file-name d)) p)) 1758 (and d (nrepl-extract-ports (cider--file-path d))))) 1759 (cons (clojure-project-dir dir) paths))))) 1760 (seq-uniq (delq nil proj-ports)))) 1761 1762 (defun cider--running-nrepl-paths () 1763 "Retrieve project paths of running nREPL servers. 1764 Use `cider-ps-running-nrepls-command' and 1765 `cider-ps-running-nrepl-path-regexp-list'." 1766 (let (paths) 1767 (with-temp-buffer 1768 (insert (shell-command-to-string cider-ps-running-nrepls-command)) 1769 (dolist (regexp cider-ps-running-nrepl-path-regexp-list) 1770 (goto-char 1) 1771 (while (re-search-forward regexp nil t) 1772 (setq paths (cons (match-string 1) paths))))) 1773 (seq-uniq paths))) 1774 1775 (defun cider--identify-buildtools-present (&optional project-dir) 1776 "Identify build systems present by their build files in PROJECT-DIR. 1777 PROJECT-DIR defaults to current project." 1778 (let* ((default-directory (or project-dir (clojure-project-dir (cider-current-dir)))) 1779 (build-files '((lein . "project.clj") 1780 (boot . "build.boot") 1781 (clojure-cli . "deps.edn") 1782 (babashka . "bb.edn") 1783 (shadow-cljs . "shadow-cljs.edn") 1784 (gradle . "build.gradle") 1785 (gradle . "build.gradle.kts") 1786 (nbb . "nbb.edn")))) 1787 (delq nil 1788 (mapcar (lambda (candidate) 1789 (when (file-exists-p (cdr candidate)) 1790 (car candidate))) 1791 build-files)))) 1792 1793 (defun cider-project-type (&optional project-dir) 1794 "Determine the type of the project in PROJECT-DIR. 1795 When multiple project file markers are present, check for a preferred build 1796 tool in `cider-preferred-build-tool', otherwise prompt the user to choose. 1797 PROJECT-DIR defaults to the current project." 1798 (let* ((choices (cider--identify-buildtools-present project-dir)) 1799 (multiple-project-choices (> (length choices) 1)) 1800 ;; this needs to be a string to be used in `completing-read' 1801 (default (symbol-name (car choices))) 1802 ;; `cider-preferred-build-tool' used to be a string prior to CIDER 1803 ;; 0.18, therefore the need for `cider-maybe-intern' 1804 (preferred-build-tool (cider-maybe-intern cider-preferred-build-tool))) 1805 (cond ((and multiple-project-choices 1806 (member preferred-build-tool choices)) 1807 preferred-build-tool) 1808 (multiple-project-choices 1809 (intern 1810 (completing-read 1811 (format "Which command should be used (default %s): " default) 1812 choices nil t nil nil default))) 1813 (choices 1814 (car choices)) 1815 ;; TODO: Move this fallback outside the project-type check 1816 ;; if we're outside a project we fallback to whatever tool 1817 ;; is specified in `cider-jack-in-default' (normally clojure-cli) 1818 ;; `cider-jack-in-default' used to be a string prior to CIDER 1819 ;; 0.18, therefore the need for `cider-maybe-intern' 1820 (t (cider-maybe-intern cider-jack-in-default))))) 1821 1822 ;;;###autoload 1823 (defun cider-jack-in-universal (arg) 1824 "Start and connect to an nREPL server for the current project or ARG project id. 1825 1826 If a project is found in current dir, call `cider-jack-in' passing ARG as 1827 first parameter, of which see. Otherwise, ask user which project type to 1828 start an nREPL server and connect to without a project. 1829 1830 But if invoked with a numeric prefix ARG, then start an nREPL server for 1831 the project type denoted by ARG number and connect to it, even if there is 1832 no project for it in the current dir. 1833 1834 The supported project tools and their assigned numeric prefix ids are 1835 sourced from `cider-jack-in-universal-options', of which see. 1836 1837 You can pass a numeric prefix argument n with `M-n` or `C-u n`. 1838 1839 For example, to jack in to leiningen which is assigned to prefix arg 2 type 1840 1841 M-2 \\[cider-jack-in-universal]." 1842 (interactive "P") 1843 (let ((cpt (clojure-project-dir (cider-current-dir)))) 1844 (if (or (integerp arg) (null cpt)) 1845 (let* ((project-types-available (mapcar #'car cider-jack-in-universal-options)) 1846 (project-type (if (null arg) 1847 (intern (completing-read 1848 "No project found in current dir, select project type to jack in: " 1849 project-types-available 1850 nil t)) 1851 1852 (or (seq-some (lambda (elt) 1853 (cl-destructuring-bind 1854 (project-type (&key prefix-arg &allow-other-keys)) elt 1855 (when (= arg prefix-arg) 1856 project-type))) 1857 cider-jack-in-universal-options) 1858 (error ":cider-jack-in-universal :unsupported-prefix-argument %S :no-such-project" 1859 arg)))) 1860 (project-options (cadr (seq-find (lambda (elt) (equal project-type (car elt))) 1861 cider-jack-in-universal-options))) 1862 (jack-in-opts (plist-get project-options :cmd)) 1863 (jack-in-type (plist-get jack-in-opts :jack-in-type))) 1864 (pcase jack-in-type 1865 ('clj (cider-jack-in-clj jack-in-opts)) 1866 ('cljs (cider-jack-in-cljs jack-in-opts)) 1867 (_ (error ":cider-jack-in-universal :jack-in-type-unsupported %S" jack-in-type)))) 1868 1869 (cider-jack-in-clj arg)))) 1870 1871 1872 ;; TODO: Implement a check for command presence over tramp 1873 (defun cider--resolve-command (command) 1874 "Find COMMAND in exec path (see variable `exec-path'). 1875 Return nil if not found. In case `default-directory' is non-local we 1876 assume the command is available." 1877 (when-let* ((command (or (and (file-remote-p default-directory) command) 1878 (executable-find command) 1879 (executable-find (concat command ".bat"))))) 1880 (shell-quote-argument command))) 1881 1882 (defun cider--resolve-project-command (command) 1883 "Find COMMAND in project dir or exec path (see variable `exec-path'). 1884 If COMMAND starts with ./ or ../ resolve relative to `clojure-project-dir', 1885 otherwise resolve via `cider--resolve-command'." 1886 (if (string-match-p "\\`\\.\\{1,2\\}/" command) 1887 (locate-file command (list (clojure-project-dir)) '("" ".bat") 'executable) 1888 (cider--resolve-command command))) 1889 1890 (defcustom cider-connection-message-fn #'cider-random-words-of-inspiration 1891 "The function to use to generate the message displayed on connect. 1892 When set to nil no additional message will be displayed. A good 1893 alternative to the default is `cider-random-tip'." 1894 :type 'function 1895 :group 'cider 1896 :package-version '(cider . "0.11.0")) 1897 1898 (defun cider--maybe-inspire-on-connect () 1899 "Display an inspiration connection message." 1900 (when cider-connection-message-fn 1901 (message "Connected! %s" (funcall cider-connection-message-fn)))) 1902 1903 (add-hook 'cider-connected-hook #'cider--maybe-inspire-on-connect) 1904 1905 ;;;###autoload 1906 (with-eval-after-load 'clojure-mode 1907 (define-key clojure-mode-map (kbd "C-c M-x") #'cider) 1908 (define-key clojure-mode-map (kbd "C-c M-j") #'cider-jack-in-clj) 1909 (define-key clojure-mode-map (kbd "C-c M-J") #'cider-jack-in-cljs) 1910 (define-key clojure-mode-map (kbd "C-c M-c") #'cider-connect-clj) 1911 (define-key clojure-mode-map (kbd "C-c M-C") #'cider-connect-cljs) 1912 (define-key clojure-mode-map (kbd "C-c C-x") 'cider-start-map) 1913 (define-key clojure-mode-map (kbd "C-c C-s") 'sesman-map) 1914 (require 'sesman) 1915 (sesman-install-menu clojure-mode-map) 1916 (add-hook 'clojure-mode-hook (lambda () (setq-local sesman-system 'CIDER)))) 1917 1918 (provide 'cider) 1919 1920 ;;; cider.el ends here