dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

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