|
43 | 43 | (require 'seq) |
44 | 44 | (require 'subr-x) |
45 | 45 |
|
| 46 | +(require 'clojure-mode) |
| 47 | +(require 'nrepl-client) |
| 48 | + |
46 | 49 | (require 'cider-util) |
47 | 50 |
|
48 | 51 | ;; Defined in cider.el; declared here to keep cider-jack-in.el free of a |
|
51 | 54 | (defvar cider-clojure-cli-aliases) |
52 | 55 | (defvar cider-clojure-cli-global-aliases) |
53 | 56 | (defvar cider-enable-nrepl-jvmti-agent) |
54 | | -(defvar cider-jack-in-tools) |
55 | | -(declare-function cider--jack-in-tool "cider") |
| 57 | +(declare-function cider--update-params "cider") |
| 58 | +(declare-function cider-connect-sibling-clj "cider") |
| 59 | +(declare-function cider-connect-sibling-cljs "cider") |
| 60 | + |
| 61 | +;; Defined in cider-cljs.el; declared here because cider-cljs.el and |
| 62 | +;; cider-jack-in.el deliberately don't require each other (both are required |
| 63 | +;; by cider.el). By the time these are called, cider.el has loaded both. |
| 64 | +(declare-function cider--update-cljs-type "cider-cljs") |
| 65 | +(declare-function cider--check-cljs "cider-cljs") |
56 | 66 |
|
57 | 67 | (defvar cider-jack-in-dependencies nil |
58 | 68 | "List of dependencies where elements are lists of artifact name and version.") |
@@ -462,6 +472,267 @@ with its nREPL middleware and dependencies." |
462 | 472 | (funcall inject params project-type command) |
463 | 473 | params))) |
464 | 474 |
|
| 475 | + |
| 476 | +;;; Command resolution |
| 477 | + |
| 478 | +(defun cider--resolve-command (command) |
| 479 | + "Find COMMAND in `exec-path', or on the remote host's PATH over TRAMP. |
| 480 | +Return the (shell-quoted) absolute path if found, otherwise nil. When |
| 481 | +`default-directory' is remote, `executable-find' is asked to search on |
| 482 | +that host instead of the local one." |
| 483 | + (let ((remote (file-remote-p default-directory))) |
| 484 | + (when-let* ((found (or (executable-find command remote) |
| 485 | + (executable-find (concat command ".bat") remote)))) |
| 486 | + (shell-quote-argument found)))) |
| 487 | + |
| 488 | +(defun cider--resolve-project-command (command) |
| 489 | + "Find COMMAND in project dir or exec path (see variable `exec-path'). |
| 490 | +If COMMAND starts with ./ or ../ resolve relative to `clojure-project-dir', |
| 491 | +otherwise resolve via `cider--resolve-command'." |
| 492 | + (if (string-match-p "\\`\\.\\{1,2\\}/" command) |
| 493 | + (locate-file command (list (clojure-project-dir)) '("" ".bat") 'executable) |
| 494 | + (cider--resolve-command command))) |
| 495 | + |
| 496 | +(defun cider--resolve-prefix-command (command) |
| 497 | + "Resolve COMMAND that may be a prefixed invocation like \"npx X\". |
| 498 | +Splits COMMAND on whitespace, resolves the first token via |
| 499 | +`cider--resolve-command', and rejoins it with the remaining tokens." |
| 500 | + (let ((parts (split-string command))) |
| 501 | + (when-let* ((resolved (cider--resolve-command (car parts)))) |
| 502 | + (mapconcat #'identity (cons resolved (cdr parts)) " ")))) |
| 503 | + |
| 504 | + |
| 505 | +;;; Jack-in tool registry |
| 506 | + |
| 507 | +(defvar cider-jack-in-tools nil |
| 508 | + "Alist of project tools known to `cider-jack-in'. |
| 509 | +Each entry has the form (PROJECT-TYPE . PLIST), where PLIST may contain: |
| 510 | +
|
| 511 | +- :command-var symbol of the variable holding the executable name. |
| 512 | +
|
| 513 | +- :params-var symbol of the variable holding the params string used to |
| 514 | + start the nREPL server. |
| 515 | +
|
| 516 | +- :project-files list of project marker file names. |
| 517 | +
|
| 518 | +- :resolver function of one argument (the command string) returning the |
| 519 | + resolved invocation, or nil to use `cider--resolve-command'. |
| 520 | +
|
| 521 | +- :inject-fn function of three arguments (PARAMS PROJECT-TYPE COMMAND) |
| 522 | + returning PARAMS with REPL deps injected. When nil, no injection is |
| 523 | + performed and PARAMS is used as-is. |
| 524 | +
|
| 525 | +- :universal-prefix-arg numeric prefix arg for `cider-jack-in-universal'. |
| 526 | + Tools without this key cannot be invoked via that command. |
| 527 | +
|
| 528 | +- :jack-in-type `clj' (the default) or `cljs'; controls which jack-in |
| 529 | + entry point `cider-jack-in-universal' calls. |
| 530 | +
|
| 531 | +- :cljs-repl-type cljs REPL type symbol, used when :jack-in-type is `cljs'. |
| 532 | +
|
| 533 | +Use `cider-register-jack-in-tool' to add or replace entries.") |
| 534 | + |
| 535 | +(defun cider-register-jack-in-tool (project-type &rest plist) |
| 536 | + "Register PROJECT-TYPE in `cider-jack-in-tools'. |
| 537 | +PLIST is the property list documented in `cider-jack-in-tools'. An |
| 538 | +existing entry for PROJECT-TYPE is replaced." |
| 539 | + (setf (alist-get project-type cider-jack-in-tools) plist)) |
| 540 | + |
| 541 | +(defun cider--jack-in-tool (project-type) |
| 542 | + "Return the plist registered for PROJECT-TYPE. |
| 543 | +Signal a `user-error' if PROJECT-TYPE is not registered." |
| 544 | + (or (alist-get project-type cider-jack-in-tools) |
| 545 | + (user-error "Unsupported project type `%S'" project-type))) |
| 546 | + |
| 547 | +(defun cider--jack-in-tool-command (spec) |
| 548 | + "Return the command for tool SPEC. |
| 549 | +Prefers a non-nil value of the :command-var, falling back to the result |
| 550 | +of :default-command-fn when the var is nil or unset. Returns nil if |
| 551 | +neither produces a value." |
| 552 | + (or (when-let* ((var (plist-get spec :command-var))) (symbol-value var)) |
| 553 | + (when-let* ((fn (plist-get spec :default-command-fn))) (funcall fn)))) |
| 554 | + |
| 555 | +(defun cider-jack-in-command (project-type) |
| 556 | + "Determine the command `cider-jack-in' needs to invoke for the PROJECT-TYPE." |
| 557 | + (or (cider--jack-in-tool-command (cider--jack-in-tool project-type)) |
| 558 | + (user-error "No command configured for project type `%S'" project-type))) |
| 559 | + |
| 560 | +(defun cider-jack-in-resolve-command (project-type) |
| 561 | + "Determine the resolved file path to `cider-jack-in-command'. |
| 562 | +Throws an error if PROJECT-TYPE is unknown." |
| 563 | + (let* ((spec (cider--jack-in-tool project-type)) |
| 564 | + (command (cider--jack-in-tool-command spec)) |
| 565 | + (resolver (or (plist-get spec :resolver) #'cider--resolve-command))) |
| 566 | + (when command |
| 567 | + (funcall resolver command)))) |
| 568 | + |
| 569 | +(defun cider-jack-in-params (project-type) |
| 570 | + "Determine the commands params for `cider-jack-in' for the PROJECT-TYPE." |
| 571 | + ;; The format of these command-line strings must consider different shells, |
| 572 | + ;; different values of IFS, and the possibility that they'll be run remotely |
| 573 | + ;; (e.g. with TRAMP). Using `", "` causes problems with TRAMP, for example. |
| 574 | + ;; Please be careful when changing them. |
| 575 | + (symbol-value (plist-get (cider--jack-in-tool project-type) :params-var))) |
| 576 | + |
| 577 | + |
| 578 | +;;; Built-in jack-in tool registrations |
| 579 | + |
| 580 | +(cider-register-jack-in-tool 'clojure-cli |
| 581 | + :command-var 'cider-clojure-cli-command |
| 582 | + :default-command-fn #'cider--default-clojure-cli-command |
| 583 | + :params-var 'cider-clojure-cli-parameters |
| 584 | + :project-files '("deps.edn") |
| 585 | + :inject-fn #'cider--clojure-cli-inject-deps |
| 586 | + :universal-prefix-arg 1) |
| 587 | + |
| 588 | +(cider-register-jack-in-tool 'lein |
| 589 | + :command-var 'cider-lein-command |
| 590 | + :params-var 'cider-lein-parameters |
| 591 | + :project-files '("project.clj") |
| 592 | + :inject-fn #'cider--lein-inject-deps |
| 593 | + :universal-prefix-arg 2) |
| 594 | + |
| 595 | +(cider-register-jack-in-tool 'babashka |
| 596 | + :command-var 'cider-babashka-command |
| 597 | + :params-var 'cider-babashka-parameters |
| 598 | + :project-files '("bb.edn") |
| 599 | + :universal-prefix-arg 3) |
| 600 | + |
| 601 | +(cider-register-jack-in-tool 'shadow-cljs |
| 602 | + :command-var 'cider-shadow-cljs-command |
| 603 | + :params-var 'cider-shadow-cljs-parameters |
| 604 | + :project-files '("shadow-cljs.edn") |
| 605 | + :resolver #'cider--resolve-prefix-command |
| 606 | + :inject-fn #'cider--shadow-cljs-inject-deps) |
| 607 | + |
| 608 | +(cider-register-jack-in-tool 'gradle |
| 609 | + :command-var 'cider-gradle-command |
| 610 | + :params-var 'cider-gradle-parameters |
| 611 | + :project-files '("build.gradle" "build.gradle.kts") |
| 612 | + :resolver #'cider--resolve-project-command |
| 613 | + :inject-fn #'cider--gradle-inject-deps) |
| 614 | + |
| 615 | +(cider-register-jack-in-tool 'nbb |
| 616 | + :command-var 'cider-nbb-command |
| 617 | + :params-var 'cider-nbb-parameters |
| 618 | + :project-files '("nbb.edn") |
| 619 | + :resolver #'cider--resolve-prefix-command |
| 620 | + :universal-prefix-arg 4 |
| 621 | + :jack-in-type 'cljs |
| 622 | + :cljs-repl-type 'nbb) |
| 623 | + |
| 624 | +(cider-register-jack-in-tool 'basilisp |
| 625 | + :command-var 'cider-basilisp-command |
| 626 | + :params-var 'cider-basilisp-parameters |
| 627 | + :project-files '("basilisp.edn") |
| 628 | + :universal-prefix-arg 5) |
| 629 | + |
| 630 | + |
| 631 | +;;; ClojureScript jack-in helpers |
| 632 | + |
| 633 | +(defmacro cider--with-cljs-jack-in-deps (&rest body) |
| 634 | + "Run BODY with the cljs jack-in deps appended to the regular ones. |
| 635 | +`cider--update-jack-in-cmd' picks up these dynamic vars indirectly when |
| 636 | +constructing the jack-in command, so they must be in effect for the |
| 637 | +duration of the param-update pipeline." |
| 638 | + (declare (indent 0) (debug t)) |
| 639 | + `(let ((cider-jack-in-dependencies |
| 640 | + (append cider-jack-in-dependencies cider-jack-in-cljs-dependencies)) |
| 641 | + (cider-jack-in-lein-plugins |
| 642 | + (append cider-jack-in-lein-plugins cider-jack-in-cljs-lein-plugins)) |
| 643 | + (cider-jack-in-nrepl-middlewares |
| 644 | + (append cider-jack-in-nrepl-middlewares cider-jack-in-cljs-nrepl-middlewares))) |
| 645 | + ,@body)) |
| 646 | + |
| 647 | +(put 'cider--with-cljs-jack-in-deps 'lisp-indent-function 0) |
| 648 | + |
| 649 | + |
| 650 | +;;; User-level Jack-in commands |
| 651 | + |
| 652 | +(defun cider--start-nrepl-server (params &optional on-port-callback) |
| 653 | + "Start an nREPL server. |
| 654 | +PARAMS is a plist optionally containing :project-dir and :jack-in-cmd. |
| 655 | +ON-PORT-CALLBACK (optional) is a function of one argument (server buffer) |
| 656 | +which is called by the process filter once the port of the connection has |
| 657 | +been determined. The callback runs in the buffer that was current at the |
| 658 | +time of this call, so that subsequent connect logic sees the correct |
| 659 | +project context even if the user has switched buffers in the meantime." |
| 660 | + (let ((orig-buffer (current-buffer))) |
| 661 | + (nrepl-start-server-process |
| 662 | + (plist-get params :project-dir) |
| 663 | + (plist-get params :jack-in-cmd) |
| 664 | + (when on-port-callback |
| 665 | + (lambda (server-buf) |
| 666 | + (if (buffer-live-p orig-buffer) |
| 667 | + (with-current-buffer orig-buffer |
| 668 | + (funcall on-port-callback server-buf)) |
| 669 | + (funcall on-port-callback server-buf))))))) |
| 670 | + |
| 671 | +;;;###autoload |
| 672 | +(defun cider-jack-in-clj (params) |
| 673 | + "Start an nREPL server for the current project and connect to it. |
| 674 | +PARAMS is a plist optionally containing :project-dir and :jack-in-cmd. |
| 675 | +With the prefix argument, allow editing of the jack in command; with a |
| 676 | +double prefix prompt for all these parameters." |
| 677 | + (interactive "P") |
| 678 | + (let ((params (cider--update-params params))) |
| 679 | + (cider--start-nrepl-server |
| 680 | + params |
| 681 | + (lambda (server-buf) |
| 682 | + (cider-connect-sibling-clj params server-buf))))) |
| 683 | + |
| 684 | +(defun cider-start-nrepl-server (params) |
| 685 | + "Start an nREPL server for the current project, but don't connect to it. |
| 686 | +PARAMS is a plist optionally containing :project-dir and :jack-in-cmd. |
| 687 | +With the prefix argument, allow editing of the start server command; with a |
| 688 | +double prefix prompt for all these parameters." |
| 689 | + (interactive "P") |
| 690 | + (cider--start-nrepl-server (cider--update-params params))) |
| 691 | + |
| 692 | +;;;###autoload |
| 693 | +(defun cider-jack-in-cljs (params) |
| 694 | + "Start an nREPL server for the current project and connect to it. |
| 695 | +PARAMS is a plist optionally containing :project-dir, :jack-in-cmd and |
| 696 | +:cljs-repl-type (e.g. `shadow', `node', `figwheel', etc). |
| 697 | +
|
| 698 | +With the prefix argument, |
| 699 | +allow editing of the jack in command; with a double prefix prompt for all |
| 700 | +these parameters." |
| 701 | + (interactive "P") |
| 702 | + (cider--with-cljs-jack-in-deps |
| 703 | + (let ((params (cider--update-params params))) |
| 704 | + (cider--start-nrepl-server |
| 705 | + params |
| 706 | + (lambda (server-buf) |
| 707 | + (cider-connect-sibling-cljs params server-buf)))))) |
| 708 | + |
| 709 | +;;;###autoload |
| 710 | +(defun cider-jack-in-clj&cljs (&optional params soft-cljs-start) |
| 711 | + "Start an nREPL server and connect with clj and cljs REPLs. |
| 712 | +PARAMS is a plist optionally containing :project-dir, :jack-in-cmd and |
| 713 | +:cljs-repl-type (e.g. `shadow', `node', `figwheel', etc). |
| 714 | +
|
| 715 | +With the prefix argument, allow for editing of the jack in command; |
| 716 | +with a double prefix prompt for all these parameters. |
| 717 | +
|
| 718 | +When SOFT-CLJS-START is non-nil, start cljs REPL |
| 719 | +only when the ClojureScript dependencies are met." |
| 720 | + (interactive "P") |
| 721 | + (cider--with-cljs-jack-in-deps |
| 722 | + (let ((params (thread-first params |
| 723 | + (cider--update-params) |
| 724 | + (cider--update-cljs-type) |
| 725 | + ;; already asked, don't ask on sibling connect |
| 726 | + (plist-put :do-prompt nil)))) |
| 727 | + (cider--start-nrepl-server |
| 728 | + params |
| 729 | + (lambda (server-buf) |
| 730 | + (let ((clj-repl (cider-connect-sibling-clj params server-buf))) |
| 731 | + (if soft-cljs-start |
| 732 | + (when (cider--check-cljs (plist-get params :cljs-repl-type) 'no-error) |
| 733 | + (cider-connect-sibling-cljs params clj-repl)) |
| 734 | + (cider-connect-sibling-cljs params clj-repl)))))))) |
| 735 | + |
465 | 736 | (provide 'cider-jack-in) |
466 | 737 |
|
467 | 738 | ;;; cider-jack-in.el ends here |
0 commit comments