Skip to content

Commit

Permalink
Make jump-to-definition work in projects needing `cider-path-translat…
Browse files Browse the repository at this point in the history
…ions`

i.e. Dockerized projects.

Fixes #3413
  • Loading branch information
vemv committed Aug 16, 2023
1 parent 4a35e24 commit f648365
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- [#3331](https://github.com/clojure-emacs/cider/issues/3331): `cider-eval`: never jump to spurious locations, as sometimes conveyed by nREPL.
- [#3112](https://github.com/clojure-emacs/cider/issues/3112): Fix the CIDER `xref-find-references` backend to return correct filenames.
- [#3393](https://github.com/clojure-emacs/cider/issues/3393): recompute namespace info on each shadow-cljs recompilation or evaluation.
- [#3413](https://github.com/clojure-emacs/cider/issues/3413): Make jump-to-definition work in projects needing `cider-path-translations` (i.e. Dockerized projects).
- Fix the `xref-find-definitions` CIDER backend to return correct filenames.
- Fix the `cider-xref-fn-deps` buttons to direct to the right file.

Expand Down
45 changes: 33 additions & 12 deletions cider-common.el
Original file line number Diff line number Diff line change
Expand Up @@ -284,22 +284,38 @@ in the container, the alist would be `((\"/src\" \"~/projects/foo/src\"))."
:group 'cider
:package-version '(cider . "0.23.0"))

(defun cider--translate-path (path direction)
"Attempt to translate the PATH in the given DIRECTION.
(defun cider--translate-path (path direction &optional return-all)
"Attempt to translate the PATH in the given DIRECTION, optionally RETURN-ALL.
Looks at `cider-path-translations' for (container . host) alist of path
prefixes and translates PATH from container to host or vice-versa depending on
whether DIRECTION is 'from-nrepl or 'to-nrepl."
(seq-let [from-fn to-fn path-fn] (cond ((eq direction 'from-nrepl) '(car cdr identity))
((eq direction 'to-nrepl) '(cdr car expand-file-name)))
(let ((path (funcall path-fn path)))
(seq-some (lambda (translation)
(let ((prefix (file-name-as-directory (expand-file-name (funcall from-fn translation)))))
(when (string-prefix-p prefix path)
(replace-regexp-in-string (format "^%s" (regexp-quote prefix))
(file-name-as-directory
(expand-file-name (funcall to-fn translation)))
path))))
cider-path-translations))))
(let ((f (lambda (translation)
(let ((path (funcall path-fn path))
(prefix (file-name-as-directory (expand-file-name (funcall from-fn translation)))))
(when (string-prefix-p prefix path)
(replace-regexp-in-string (format "^%s" (regexp-quote prefix))
(file-name-as-directory
(expand-file-name (funcall to-fn translation)))
path))))))
(if return-all
(seq-filter #'identity (mapcar f cider-path-translations))
(seq-some f cider-path-translations)))))

(defun cider--all-path-translations ()
"Returns `cider-path-translations' if non-empty, else seeks a present value."
(or cider-path-translations
;; cider-path-translations often is defined as a directory-local variable,
;; so after jumping to a .jar file, its value can be lost,
;; so we have to figure out a possible translation:
(thread-last (buffer-list)
(seq-map (lambda (buffer)
(buffer-local-value 'cider-path-translations buffer)))
(seq-filter #'identity)
(seq-uniq)
(apply #'append)
(seq-uniq))))

(defun cider--translate-path-from-nrepl (path)
"Attempt to translate the nREPL PATH to a local path."
Expand Down Expand Up @@ -334,7 +350,12 @@ If no local or remote file exists, return nil."
((and tramp-path (file-exists-p tramp-path))
tramp-path)
((and local-path (file-exists-p local-path))
local-path))))
local-path)
(t
(when-let* ((cider-path-translations (cider--all-path-translations)))
(thread-last (cider--translate-path local-path 'from-nrepl :return-all)
(seq-filter #'file-exists-p)
car))))))

(declare-function archive-extract "arc-mode")
(declare-function archive-zip-extract "arc-mode")
Expand Down
34 changes: 31 additions & 3 deletions cider-connection.el
Original file line number Diff line number Diff line change
Expand Up @@ -614,8 +614,9 @@ REPL defaults to the current REPL."
(sesman-more-recent-p (cdr session1) (cdr session2)))

(declare-function cider-classpath-entries "cider-client")
(cl-defmethod sesman-friendly-session-p ((_system (eql CIDER)) session)
"Check if SESSION is a friendly session."

(defun cider--sesman-friendly-session-p (session &optional debug)
"Check if SESSION is a friendly session, DEBUG optionally."
(setcdr session (seq-filter #'buffer-live-p (cdr session)))
(when-let* ((repl (cadr session))
(proc (get-buffer-process repl))
Expand All @@ -642,7 +643,34 @@ REPL defaults to the current REPL."
(or (seq-find (lambda (path) (string-prefix-p path file))
classpath)
(seq-find (lambda (path) (string-prefix-p path file))
classpath-roots))))))
classpath-roots)
(when-let* ((cider-path-translations (cider--all-path-translations))
(translated (cider--translate-path file 'to-nrepl :return-all)))
(seq-find (lambda (translated-path)
(or (seq-find (lambda (path)
(string-prefix-p path translated-path))
classpath)
(seq-find (lambda (path)
(string-prefix-p path translated-path))
classpath-roots)))
translated))
(when debug
(list file "was not determined to belong to classpath:" classpath "or classpath-roots:" classpath-roots)))))))

(defun cider-debug-sesman-friendly-session-p ()
"`message's debugging information relative to friendly sessions.
This is useful for when one sees 'No linked CIDER sessions'
in an unexpected place."
(interactive)
(message (prin1-to-string (mapcar (lambda (session)
(cider--sesman-friendly-session-p session
t))
(sesman--all-system-sessions 'CIDER)))))

(cl-defmethod sesman-friendly-session-p ((_system (eql CIDER)) session)
"Check if SESSION is a friendly session."
(cider--sesman-friendly-session-p session))

(defvar cider-sesman-browser-map
(let ((map (make-sparse-keymap)))
Expand Down
4 changes: 4 additions & 0 deletions dev/docker-sample-project/.dir-locals.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
((nil . ((eval . (customize-set-variable 'cider-path-translations
(list
(cons "/src" (clojure-project-dir))
(cons "/root/.m2" (concat (getenv "HOME") "/.m2"))))))))
9 changes: 9 additions & 0 deletions dev/docker-sample-project/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM clojure:temurin-17-lein-bullseye
ENV DEBIAN_FRONTEND=noninteractive
ENV NREPL_PORT=7888
WORKDIR /root/app
COPY . /root/app
RUN lein deps
EXPOSE 7888
RUN lein classpath
CMD ["lein", "repl", ":headless", ":host", "0.0.0.0", ":port", "7888"]
5 changes: 5 additions & 0 deletions dev/docker-sample-project/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build:
DOCKER_BUILDKIT=0 docker build --no-cache -t cider-docker-dev .

run: build
docker run -v $$PWD/src:/app/src -p 7888:7888 cider-docker-dev
14 changes: 14 additions & 0 deletions dev/docker-sample-project/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
This project spins up a Clojure project within a Docker image.

The Docker image exposes an nREPL server.

This way, for development purposes, we can exercise CIDER's Docker-related capabilities.

To get started:

* From a terminal tab, run `make run` to run the Docker image
* Note that it has a volume mapping for `src`, so any local changes will be visible in the Docker image.
* Also note that the root of this subproject has a .dir-locals.el setting up `cider-path-translations`.
* `M-x cider-connect-clj`, choose localhost, 7888
* `M-x cider-load-buffer` the foo.clj namespace.
* From now on, you can `M-.` (jump to definition) recursively, starting from `clj-http.client`.
5 changes: 5 additions & 0 deletions dev/docker-sample-project/project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(defproject cider-docker-dev "0"
:dependencies [[org.clojure/clojure "1.11.1"]
[clj-http "3.12.3"]]
:source-paths ["src"]
:plugins [[cider/cider-nrepl "0.35.0"]])
2 changes: 2 additions & 0 deletions dev/docker-sample-project/src/bar.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(ns bar
(:require [foo]))
3 changes: 3 additions & 0 deletions dev/docker-sample-project/src/foo.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(ns foo
(:require
[clj-http.client :as client]))
7 changes: 4 additions & 3 deletions doc/modules/ROOT/pages/config/basic_config.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,10 @@ To prefer local resources to remote resources (tramp) when both are available:

== Translate File Paths

If you wish to translate file paths from your running instance you may use the
`cider-path-translations` defcustom to do so. For instance, suppose your app is
running in a docker container with your source directories mounted there. The
If you are running Clojure within a Docker image, or doing something similar (i.e. you're `cider-connect`ing to a process,
and there's a directory mapping for your source paths), you typically need to set `cider-path-translations`
for jump-to-definition to properly work. For instance, suppose your app is
running in a docker container with your source directories mounted there as volumes. The
navigation paths you'd get from nREPL will be relative to the source in the
docker container rather than the correct path on your host machine. You can add
translation mappings easily by setting the following (typically in `.dir-locals.el`):
Expand Down

0 comments on commit f648365

Please sign in to comment.