From 223bf407dc02448dc82a4d3d64ca21eda786350c Mon Sep 17 00:00:00 2001 From: lycheese Date: Mon, 16 May 2022 10:56:34 +0200 Subject: [PATCH] Allow combining sesman sessions of a project - Makes cider-repls return the combination of all sesman sessions or those sessions with same host associated with a project - Adds a custom vars for toggling the new functionality Closes clojure-emacs/cider#2946. --- CHANGELOG.md | 2 + cider-connection.el | 61 +++++++++++++- .../pages/usage/managing_connections.adoc | 28 ++++++- test/cider-connection-tests.el | 84 +++++++++++++++++++ 4 files changed, 169 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a17b97bc..82e6e67d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ * [#3195](https://github.com/clojure-emacs/cider/issues/3195): Revert the change that resulted in `(error "Cyclic keymap inheritance")` on `cider-test-run-test`. +* [#2946](https://github.com/clojure-emacs/cider/issues/2946): Add custom var `cider-combine-sesman-sessions-per-project` to allow combining all sesman sessions associated with a project, therefore allowing for jacking-in a clojure-cli clj repl and a shadow-cljs cljs repl with separate dependency management and automatically using the correct repl even though the repls are in different sesman sessions. (CAUTION: If you are using two separate sessions for local and remote development setting this var to `true` will send all evaluated commands to *both* sessions. If this is functionality you need, use `cider-combine-sesman-sessions-per-project-per-host` instead which allows combining only those sessions which have the same host associated with them.) + ## 1.4.0 (2022-05-02) ## New features diff --git a/cider-connection.el b/cider-connection.el index 77326e1e9..137e3086c 100644 --- a/cider-connection.el +++ b/cider-connection.el @@ -61,6 +61,19 @@ available) and the matching REPL buffer." :safe #'booleanp :package-version '(cider . "0.9.0")) +;;;###autoload +(defcustom cider-merge-sessions nil + "Controls session combination behaviour. + +`:host' combines all sessions of a project associated with the same host. +`:project' combines all sessions of a project. + +All other values do not combine any sessions." + :type 'keyword + :group 'cider + :safe #'keywordp + :package-version '(cider . "1.5")) + (defconst cider-required-nrepl-version "0.6.0" "The minimum nREPL version that's known to work properly with CIDER.") @@ -862,6 +875,33 @@ no linked session or there is no REPL of TYPE within the current session." ((listp type) (member buffer-repl-type type)) (t (string= type buffer-repl-type))))) +(defun cider--get-host-from-session (session) + "Returns the host associated with SESSION." + (plist-get (cider--gather-session-params session) + :host)) + +(defun cider--make-sessions-list-with-hosts (sessions) + "Makes a list of SESSIONS and their hosts. +Returns a list of the form ((session1 host1) (session2 host2) ...)." + (mapcar (lambda (session) + (list session (cider--get-host-from-session session))) + sessions)) + +(defun cider--get-sessions-with-same-host (session sessions) + "Returns a list of SESSIONS with the same host as SESSION." + (mapcar #'car + (seq-filter (lambda (x) + (string-equal (cadr x) + (cider--get-host-from-session session))) + (cider--make-sessions-list-with-hosts sessions)))) + +(defun cider--extract-connections (sessions) + "Returns a flattened list of all session buffers in SESSIONS." + (cl-reduce (lambda (x y) + (append x (cdr y))) + sessions + :initial-value '())) + (defun cider-repls (&optional type ensure) "Return cider REPLs of TYPE from the current session. If TYPE is nil or multi, return all REPLs. If TYPE is a list of types, @@ -871,9 +911,24 @@ throw an error if no linked session exists." ((listp type) (mapcar #'cider-maybe-intern type)) ((cider-maybe-intern type)))) - (repls (cdr (if ensure - (sesman-ensure-session 'CIDER) - (sesman-current-session 'CIDER))))) + (repls (pcase cider-merge-sessions + (:host + (if ensure + (or (cider--extract-connections (cider--get-sessions-with-same-host + (sesman-current-session 'CIDER) + (sesman-current-sessions 'CIDER))) + (user-error "No linked %s sessions" 'CIDER)) + (cider--extract-connections (cider--get-sessions-with-same-host + (sesman-current-session 'CIDER) + (sesman-current-sessions 'CIDER))))) + (:project + (if ensure + (or (cider--extract-connections (sesman-current-sessions 'CIDER)) + (user-error "No linked %s sessions" 'CIDER)) + (cider--extract-connections (sesman-current-sessions 'CIDER)))) + (_ (cdr (if ensure + (sesman-ensure-session 'CIDER) + (sesman-current-session 'CIDER))))))) (or (seq-filter (lambda (b) (cider--match-repl-type type b)) repls) diff --git a/doc/modules/ROOT/pages/usage/managing_connections.adoc b/doc/modules/ROOT/pages/usage/managing_connections.adoc index 57d7c3699..910eba3fe 100644 --- a/doc/modules/ROOT/pages/usage/managing_connections.adoc +++ b/doc/modules/ROOT/pages/usage/managing_connections.adoc @@ -71,11 +71,33 @@ You can add new REPLs to the current session with: A very common use-case would be to run `cider-jack-in-clj` for some project and then follow up with `cider-connect-sibling-cljs`. -NOTE: Unless there are both Clojure and ClojureScript REPLs in the same session smart-dispatch commands (e.g. evaluate the code -in the right Clojure/ClojureScript REPL, toggle between Clojure and ClojureScript REPL) won't work. A very common problem -newcomers experience is to create a Clojure REPL and a ClojureScript REPL in separate sessions and wonder why those are not +[NOTE] +==== +Unless there are both Clojure and ClojureScript REPLs in the same +session smart-dispatch commands (e.g. evaluate the code in the right +Clojure/ClojureScript REPL, toggle between Clojure and ClojureScript REPL) won't +work. A very common problem newcomers experience is to create a Clojure REPL and +a ClojureScript REPL in separate sessions and wonder why those are not interacting properly with one another. +In the case of using separate config files for the clj and cljs dependencies +(e.g. clj dependencies in `deps.edn` and cljs dependencies in `shadow-cljs.edn`) +it is currently impossible to group those two repls in the same session. +However, this can be worked around by setting +`cider-combine-sesman-sessions-per-project` to a non-nil value. This has the +effect of dispatching to all relevant repls (i.e. all clj repls for a `.clj` +buffer) instead of just those associated with the current session. It is +recommended to set this as needed on a per-project basis in `.dir-locals.el`. + +The downside of `cider-combine-sesman-sessions-per-project` is that dispatching +to separate local and remote sessions is no longer possible since all +project-associated sessions essentially become a single big session. Should this +behaviour be desired `cider-combine-sesman-sessions-per-project-per-host` can be +used instead. This var takes precedence over +`cider-combine-sesman-sessions-per-project` and only combines those sessions +which have the same host associated with them. +==== + === Session Life-Cycle Management Session life-cycle management commands live on the https://github.com/vspinu/sesman[Sesman] keymap (kbd:[C-c C-s]) diff --git a/test/cider-connection-tests.el b/test/cider-connection-tests.el index bb0c3283e..cd1d7d8a9 100644 --- a/test/cider-connection-tests.el +++ b/test/cider-connection-tests.el @@ -272,6 +272,90 @@ (expect (cider-repls) :to-equal (list bb2 bb1)) (expect (cider-repls 'cljs) :to-equal (list bb2))))))))))))) + (describe "when multiple sessions exist and cider-merge-sessions is set to :project" + (it "always returns all connections associated with a project" + (let ((proj-dir (expand-file-name "/tmp/proj-dir")) + (cider-merge-sessions :project)) + (let ((default-directory proj-dir)) + (with-repl-buffer ses-name 'clj bb1 + (with-repl-buffer ses-name 'cljs bb2 + (with-repl-buffer ses-name2 'clj b1 + (with-repl-buffer ses-name2 'cljs b2 + + (expect (cider-repls) :to-have-same-items-as (list b2 b1 bb2 bb1)) + + (switch-to-buffer bb1) + (expect (cider-repls) :to-have-same-items-as (list b2 b1 bb2 bb1)) + + ;; follows type arguments + (expect (cider-repls 'clj) :to-have-same-items-as (list b1 bb1)) + (expect (cider-repls 'cljs) :to-have-same-items-as (list b2 bb2)) + + (switch-to-buffer bb2) + ;; follows file type + (with-temp-buffer + (setq major-mode 'clojure-mode) + (expect (cider-repls) :to-have-same-items-as (list b2 b1 bb2 bb1)) + (expect (cider-repls 'clj) :to-have-same-items-as (list b1 bb1))) + + (with-temp-buffer + (setq major-mode 'clojurescript-mode) + (expect (cider-repls) :to-have-same-items-as (list b2 b1 bb2 bb1)) + (expect (cider-repls 'cljs) :to-have-same-items-as (list b2 bb2)))))))))) + (it "only returns the connections of the active project" + (let ((a-dir (expand-file-name "/tmp/a-dir")) + (b-dir (expand-file-name "/tmp/b-dir")) + (cider-merge-sessions :project)) + (let ((default-directory a-dir)) + (with-repl-buffer ses-name 'clj bb1 + (with-repl-buffer ses-name 'cljs bb2 + (let ((default-directory b-dir)) + (with-repl-buffer ses-name2 'clj b1 + (with-repl-buffer ses-name2 'cljs b2 + + (expect (cider-repls) :to-have-same-items-as (list b2 b1)) + + (switch-to-buffer bb1) + (expect (cider-repls) :to-have-same-items-as (list bb2 bb1)) + + ;; follows type arguments + (expect (cider-repls 'clj) :to-have-same-items-as (list bb1)) + (expect (cider-repls 'cljs) :to-have-same-items-as (list bb2)) + + (switch-to-buffer bb2) + ;; follows file type + (let ((default-directory b-dir)) + (with-temp-buffer + (setq major-mode 'clojure-mode) + (expect (cider-repls) :to-have-same-items-as (list b2 b1)) + (expect (cider-repls 'clj) :to-have-same-items-as (list b1)))) + + (let ((default-directory a-dir)) + (with-temp-buffer + (setq major-mode 'clojurescript-mode) + (expect (cider-repls) :to-have-same-items-as (list bb2 bb1)) + (expect (cider-repls 'cljs) :to-have-same-items-as (list bb2))))))))))))) + + (describe "when multiple sessions exist and cider-combine-merge-sessions is set to :host" + (before-each + (spy-on 'cider--gather-session-params :and-call-fake (lambda (session) + (if (string-equal (car session) "local") + '(:host "localhost") + '(:host "remotehost"))))) + (it "returns only the sessions associated with the current session's host" + (let ((cider-merge-sessions :host) + (local-session "local") + (remote-session "remote") + (proj-dir (expand-file-name "/tmp/proj-dir"))) + (let ((default-directory proj-dir)) + (with-repl-buffer local-session 'clj l1 + (with-repl-buffer local-session 'clj l2 + (with-repl-buffer remote-session 'clj r1 + (switch-to-buffer r1) + (expect (cider-repls) :to-have-same-items-as (list r1)) + (switch-to-buffer l1) + (expect (cider-repls) :to-have-same-items-as (list l1 l2))))))))) + (describe "killed buffers" (it "do not show up in it" (let ((default-directory (expand-file-name "/tmp/some-dir")))