summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--nrepl-client.el1115
-rw-r--r--nrepl-eldoc.el121
-rw-r--r--nrepl-interaction-mode.el2
-rw-r--r--nrepl-interaction.el1035
-rw-r--r--nrepl-repl.el1
-rw-r--r--nrepl.el2
6 files changed, 1182 insertions, 1094 deletions
diff --git a/nrepl-client.el b/nrepl-client.el
index 84a69e65..8c1ae651 100644
--- a/nrepl-client.el
+++ b/nrepl-client.el
@@ -34,13 +34,9 @@
(require 'dash)
(require 'thingatpt)
(require 'etags)
-(require 'arc-mode)
(require 'ansi-color)
-(require 'eldoc)
(require 'ewoc)
(require 'cl-lib)
-(require 'compile)
-(require 'tramp)
;;; Compatibility
@@ -82,6 +78,24 @@
:type 'string
:group 'nrepl)
+(defcustom nrepl-lein-command
+ "lein"
+ "The command used to execute leiningen 2.x."
+ :type 'string
+ :group 'nrepl-repl-mode)
+
+(defcustom nrepl-server-command
+ (if (or (locate-file nrepl-lein-command exec-path)
+ (locate-file (format "%s.bat" nrepl-lein-command) exec-path))
+ (format "%s repl :headless" nrepl-lein-command)
+ (format "echo \"%s repl :headless\" | eval $SHELL -l" nrepl-lein-command))
+ "The command used to start the nREPL via command `nrepl-jack-in'.
+For a remote nREPL server lein must be in your PATH. The remote
+proc is launched via sh rather than bash, so it might be necessary
+to specific the full path to it. Localhost is assumed."
+ :type 'string
+ :group 'nrepl-repl-mode)
+
(defvar nrepl-repl-requires-sexp "(apply require '[[clojure.repl :refer (source apropos dir pst doc find-doc)] [clojure.java.javadoc :refer (javadoc)] [clojure.pprint :refer (pp pprint)]])"
"Things to require in the tooling session and the REPL buffer.")
@@ -90,10 +104,7 @@
(defvar nrepl-repl-buffer nil)
(defvar nrepl-endpoint nil)
(defvar nrepl-project-dir nil)
-(defconst nrepl-error-buffer "*nrepl-error*")
-(defconst nrepl-doc-buffer "*nrepl-doc*")
-(defconst nrepl-src-buffer "*nrepl-src*")
-(defconst nrepl-result-buffer "*nrepl-result*")
+
(defconst nrepl-repl-buffer-name-template "*nrepl%s*")
(defconst nrepl-connection-buffer-name-template "*nrepl-connection%s*")
(defconst nrepl-server-buffer-name-template "*nrepl-server%s*")
@@ -110,7 +121,7 @@ buffer will be hidden.")
(defun nrepl-buffer-name (buffer-name-template)
"Generate a buffer name using BUFFER-NAME-TEMPLATE.
-The name will include the project name if available. The name will
+The name will include the project name if available. The name will
also include the connection port if `nrepl-buffer-name-show-port' is true."
(generate-new-buffer-name
(let ((project-name (nrepl--project-name nrepl-project-dir))
@@ -132,21 +143,6 @@ also include the connection port if `nrepl-buffer-name-show-port' is true."
(nrepl-apply-hide-special-buffers
(nrepl-buffer-name nrepl-server-buffer-name-template)))
-
-(defface nrepl-error-highlight-face
- '((((supports :underline (:style wave)))
- (:underline (:style wave :color "red") :inherit unspecified))
- (t (:inherit font-lock-warning-face :underline t)))
- "Face used to highlight compilation errors in Clojure buffers."
- :group 'nrepl)
-
-(defface nrepl-warning-highlight-face
- '((((supports :underline (:style wave)))
- (:underline (:style wave :color "yellow") :inherit unspecified))
- (t (:inherit font-lock-warning-face :underline (:color "yellow"))))
- "Face used to highlight compilation warnings in Clojure buffers."
- :group 'nrepl)
-
;; buffer local declarations
(defvar nrepl-session nil
"Current nREPL session id.")
@@ -169,32 +165,9 @@ To be used for tooling calls (i.e. completion, eldoc, etc)")
(defvar nrepl-err-handler 'nrepl-default-err-handler
"Evaluation error handler.")
-(defvar nrepl-extra-eldoc-commands '("nrepl-complete" "yas/expand")
- "Extra commands to be added to eldoc's safe commands list.")
-
(defvar nrepl-ops nil
"Available nREPL server ops (from describe).")
-(defcustom nrepl-popup-stacktraces t
- "Non-nil means pop-up error stacktraces for evaluation errors.
-Nil means show only an error message in the minibuffer. See also
-`nrepl-popup-stacktraces-in-repl', which overrides this setting
-for REPL buffers."
- :type 'boolean
- :group 'nrepl)
-
-(defcustom nrepl-popup-on-error t
- "When `nrepl-popup-on-error' is set to t, stacktraces will be displayed.
-When set to nil, stactraces will not be displayed, but will be available
-in the `nrepl-error-buffer', which defaults to *nrepl-error*."
- :type 'boolean
- :group 'nrepl)
-
-(defcustom nrepl-auto-select-error-buffer nil
- "Controls whether to auto-select the error popup buffer."
- :type 'boolean
- :group 'nrepl)
-
(defcustom nrepl-buffer-name-separator " "
"Used in constructing the REPL buffer name.
The `nrepl-buffer-name-separator' separates `nrepl' from the project name."
@@ -281,372 +254,6 @@ The `nrepl-buffer-name-separator' separates `nrepl' from the project name."
"Encode with bencode MESSAGE."
(concat "d" (apply 'concat (mapcar 'nrepl-netstring message)) "e"))
-(defun nrepl-eval-region (start end)
- "Evaluate the region.
-The two arguments START and END are character positions;
-they can be in either order."
- (interactive "r")
- (nrepl-interactive-eval (buffer-substring-no-properties start end)))
-
-(defun nrepl-eval-buffer ()
- "Evaluate the current buffer."
- (interactive)
- (nrepl-eval-region (point-min) (point-max)))
-
-(defun nrepl-expression-at-point ()
- "Return the text of the expr at point."
- (apply #'buffer-substring-no-properties
- (nrepl-region-for-expression-at-point)))
-
-(defun nrepl-region-for-expression-at-point ()
- "Return the start and end position of defun at point."
- (save-excursion
- (save-match-data
- (end-of-defun)
- (let ((end (point)))
- (beginning-of-defun)
- (list (point) end)))))
-
-(defun nrepl-eval-expression-at-point (&optional prefix)
- "Evaluate the current toplevel form, and print result in the mini-buffer.
-With a PREFIX argument, print the result in the current buffer."
- (interactive "P")
- (let ((form (nrepl-expression-at-point)))
- (if prefix
- (nrepl-interactive-eval-print form)
- (nrepl-interactive-eval form))))
-
-(defun nrepl-eval-ns-form ()
- "Evaluate the current buffer's namespace form."
- (interactive)
- (when (clojure-find-ns)
- (save-excursion
- (goto-char (match-beginning 0))
- (nrepl-eval-expression-at-point))))
-
-(defun nrepl-bounds-of-sexp-at-point ()
- "Return the bounds sexp at point as a pair (or nil)."
- (or (and (equal (char-after) ?\()
- (member (char-before) '(?\' ?\, ?\@))
- ;; hide stuff before ( to avoid quirks with '( etc.
- (save-restriction
- (narrow-to-region (point) (point-max))
- (bounds-of-thing-at-point 'sexp)))
- (bounds-of-thing-at-point 'sexp)))
-
-(defun nrepl-sexp-at-point ()
- "Return the sexp at point as a string, otherwise nil."
- (let ((bounds (nrepl-bounds-of-sexp-at-point)))
- (if bounds
- (buffer-substring-no-properties (car bounds)
- (cdr bounds)))))
-
-(defun nrepl-sexp-at-point-with-bounds ()
- "Return a list containing the sexp at point and its bounds."
- (let ((bounds (nrepl-bounds-of-sexp-at-point)))
- (if bounds
- (let ((start (car bounds))
- (end (cdr bounds)))
- (list (buffer-substring-no-properties start end)
- (cons (set-marker (make-marker) start)
- (set-marker (make-marker) end)))))))
-
-(defun nrepl-last-expression ()
- "Return the last sexp."
- (buffer-substring-no-properties
- (save-excursion (backward-sexp) (point))
- (point)))
-
-(defcustom nrepl-use-local-resources t
- "Use local resources under HOME if possible."
- :type 'boolean
- :group 'nrepl)
-
-(defun nrepl-tramp-prefix ()
- "Top element on `find-tag-marker-ring` used to determine Clojure host."
- (let ((jump-origin (buffer-file-name
- (marker-buffer
- (ring-ref find-tag-marker-ring 0)))))
- (when (tramp-tramp-file-p jump-origin)
- (let ((vec (tramp-dissect-file-name jump-origin)))
- (tramp-make-tramp-file-name (tramp-file-name-method vec)
- (tramp-file-name-user vec)
- (tramp-file-name-host vec)
- nil)))))
-
-(defun nrepl-home-prefix-adjustment (resource)
- "System-dependent HOME location will be adjusted in RESOURCE.
-Removes any leading slash if on Windows."
- (save-match-data
- (cond ((string-match "^\\/\\(Users\\|home\\)\\/\\w+\\(\\/.+\\)" resource)
- (concat (getenv "HOME") (match-string 2 resource)))
- ((and (eq system-type 'windows-nt)
- (string-match "^/" resource)
- (not (tramp-tramp-file-p resource)))
- (substring resource 1))
- (t
- resource))))
-
-(defun nrepl-emacs-or-clojure-side-adjustment (resource)
- "Fix the RESOURCE path depending on `nrepl-use-local-resources`."
- (let ((resource (nrepl-home-prefix-adjustment resource))
- (clojure-side-res (concat (nrepl-tramp-prefix) resource))
- (emacs-side-res resource))
- (cond ((equal resource "") resource)
- ((and nrepl-use-local-resources
- (file-exists-p emacs-side-res))
- emacs-side-res)
- ((file-exists-p clojure-side-res)
- clojure-side-res)
- (t
- resource))))
-
-(defun nrepl-find-file (filename)
- "Switch to a buffer visiting FILENAME.
-Adjusts for HOME location using `nrepl-home-prefix-adjustment'. Uses `find-file'."
- (find-file (nrepl-emacs-or-clojure-side-adjustment filename)))
-
-(defun nrepl-find-resource (resource)
- "Find and display RESOURCE."
- (cond ((string-match "^file:\\(.+\\)" resource)
- (nrepl-find-file (match-string 1 resource)))
- ((string-match "^\\(jar\\|zip\\):file:\\(.+\\)!/\\(.+\\)" resource)
- (let* ((jar (match-string 2 resource))
- (path (match-string 3 resource))
- (buffer-already-open (get-buffer (file-name-nondirectory jar))))
- (nrepl-find-file jar)
- (goto-char (point-min))
- (search-forward path)
- (let ((opened-buffer (current-buffer)))
- (archive-extract)
- (when (not buffer-already-open)
- (kill-buffer opened-buffer)))))
- (t (error "Unknown resource path %s" resource))))
-
-(defun nrepl-jump-to-def-for (location)
- "Jump to LOCATION's definition in the source code."
- ;; ugh; elisp destructuring doesn't work for vectors
- (let ((resource (aref location 0))
- (path (aref location 1))
- (line (aref location 2)))
- (if (and path (file-exists-p path))
- (find-file path)
- (nrepl-find-resource resource))
- (goto-char (point-min))
- (forward-line (1- line))))
-
-(defun nrepl-jump-to-def-handler (buffer)
- "Create a handler for jump-to-def in BUFFER."
- ;; TODO: got to be a simpler way to do this
- (nrepl-make-response-handler buffer
- (lambda (buffer value)
- (with-current-buffer buffer
- (ring-insert find-tag-marker-ring (point-marker)))
- (nrepl-jump-to-def-for
- (car (read-from-string value))))
- (lambda (buffer out) (message out))
- (lambda (buffer err) (message err))
- nil))
-
-(defun nrepl-jump-to-def (var)
- "Jump to the definition of the VAR at point."
- (let ((form (format "(let [ns-symbol '%s
- ns-var '%s
- ns-file (clojure.core/comp :file
- clojure.core/meta
- clojure.core/second
- clojure.core/first
- clojure.core/ns-publics)
- resource-str (clojure.core/comp clojure.core/str
- clojure.java.io/resource
- ns-file)
- file-str (clojure.core/comp clojure.core/str
- clojure.java.io/file
- ns-file)]
- (cond ((clojure.core/ns-aliases ns-symbol) ns-var)
- (let [resolved-ns ((clojure.core/ns-aliases ns-symbol) ns-var)]
- [(resource-str resolved-ns)
- (file-str resolved-ns)
- 1])
-
- (find-ns ns-var)
- [(resource-str ns-var)
- (file-str ns-var)
- 1]
-
- (clojure.core/ns-resolve ns-symbol ns-var)
- ((clojure.core/juxt
- (clojure.core/comp clojure.core/str
- clojure.java.io/resource
- :file)
- (clojure.core/comp clojure.core/str
- clojure.java.io/file
- :file)
- :line)
- (clojure.core/meta (clojure.core/ns-resolve ns-symbol ns-var)))))"
- (nrepl-current-ns) var)))
- (nrepl-send-string form
- (nrepl-jump-to-def-handler (current-buffer))
- nrepl-buffer-ns
- (nrepl-current-tooling-session))))
-
-(defun nrepl-jump (query)
- "Jump to the definition of QUERY."
- (interactive "P")
- (nrepl-read-symbol-name "Symbol: " 'nrepl-jump-to-def query))
-
-(defalias 'nrepl-jump-back 'pop-tag-mark)
-
-(defun nrepl-completion-complete-core-fn (str)
- "Return a list of completions for STR using complete.core/completions."
- (let ((strlst (plist-get
- (nrepl-send-string-sync
- (format "(require 'complete.core) (complete.core/completions \"%s\" *ns*)" str)
- nrepl-buffer-ns
- (nrepl-current-tooling-session))
- :value)))
- (when strlst
- (car (read-from-string strlst)))))
-
-(defun nrepl-completion-complete-op-fn (str)
- "Return a list of completions for STR using the nREPL \"complete\" op."
- (lexical-let ((strlst (plist-get
- (nrepl-send-request-sync
- (list "op" "complete"
- "session" (nrepl-current-tooling-session)
- "ns" nrepl-buffer-ns
- "symbol" str))
- :value)))
- (when strlst
- (car strlst))))
-
-(defun nrepl-dispatch-complete-symbol (str)
- "Return a list of completions for STR.
-Dispatch to the nREPL \"complete\" op if supported,
-otherwise dispatch to internal completion function."
- (if (nrepl-op-supported-p "complete")
- (nrepl-completion-complete-op-fn str)
- (nrepl-completion-complete-core-fn str)))
-
-(defun nrepl-complete-at-point ()
- "Complete the symbol at point."
- (let ((sap (symbol-at-point)))
- (when (and sap (not (in-string-p)))
- (let ((bounds (bounds-of-thing-at-point 'symbol)))
- (list (car bounds) (cdr bounds)
- (completion-table-dynamic #'nrepl-dispatch-complete-symbol))))))
-
-(defun nrepl-eldoc-format-thing (thing)
- "Format the eldoc THING."
- (propertize thing 'face 'font-lock-function-name-face))
-
-(defun nrepl-highlight-args (arglist pos)
- "Format the the function ARGLIST for eldoc.
-POS is the index of the currently highlighted argument."
- (let* ((rest-pos (nrepl--find-rest-args-position arglist))
- (i 0))
- (mapconcat
- (lambda (arg)
- (let ((argstr (format "%s" arg)))
- (if (eq arg '&)
- argstr
- (prog1
- (if (or (= (1+ i) pos)
- (and rest-pos (> (+ 1 i) rest-pos)
- (> pos rest-pos)))
- (propertize argstr 'face
- 'eldoc-highlight-function-argument)
- argstr)
- (setq i (1+ i)))))) arglist " ")))
-
-(defun nrepl--find-rest-args-position (arglist)
- "Find the position of & in the ARGLIST vector."
- (-elem-index '& (append arglist ())))
-
-(defun nrepl-highlight-arglist (arglist pos)
- "Format the ARGLIST for eldoc.
-POS is the index of the argument to highlight."
- (concat "[" (nrepl-highlight-args arglist pos) "]"))
-
-(defun nrepl-eldoc-format-arglist (arglist pos)
- "Format all the ARGLIST for eldoc.
-POS is the index of current argument."
- (concat "("
- (mapconcat (lambda (args) (nrepl-highlight-arglist args pos))
- (read arglist) " ") ")"))
-
-(defun nrepl-eldoc-info-in-current-sexp ()
- "Return a list of the current sexp and the current argument index."
- (save-excursion
- (let ((argument-index (1- (eldoc-beginning-of-sexp))))
- ;; If we are at the beginning of function name, this will be -1.
- (when (< argument-index 0)
- (setq argument-index 0))
- ;; Don't do anything if current word is inside a string.
- (if (= (or (char-after (1- (point))) 0) ?\")
- nil
- (list (nrepl-symbol-at-point) argument-index)))))
-
-(defun nrepl-eldoc ()
- "Backend function for eldoc to show argument list in the echo area."
- (when (nrepl-current-connection-buffer)
- (let* ((info (nrepl-eldoc-info-in-current-sexp))
- (thing (car info))
- (pos (cadr info))
- (form (format "(try
- (:arglists
- (clojure.core/meta
- (clojure.core/resolve
- (clojure.core/read-string \"%s\"))))
- (catch Throwable t nil))" thing))
- (result (when thing
- (nrepl-send-string-sync form
- nrepl-buffer-ns
- (nrepl-current-tooling-session))))
- (value (plist-get result :value)))
- (unless (string= value "nil")
- (format "%s: %s"
- (nrepl-eldoc-format-thing thing)
- (nrepl-eldoc-format-arglist value pos))))))
-
-(defun nrepl-turn-on-eldoc-mode ()
- "Turn on eldoc mode in the current buffer."
- (setq-local eldoc-documentation-function 'nrepl-eldoc)
- (apply 'eldoc-add-command nrepl-extra-eldoc-commands)
- (turn-on-eldoc-mode))
-
-;;; JavaDoc Browsing
-;;; Assumes local-paths are accessible in the VM.
-(defvar nrepl-javadoc-local-paths nil
- "List of paths to directories with Javadoc.")
-
-(defun nrepl-javadoc-op (symbol-name)
- "Invoke the nREPL \"javadoc\" op on SYMBOL-NAME."
- (nrepl-send-op
- "javadoc"
- `("symbol" ,symbol-name "ns" ,nrepl-buffer-ns
- "local-paths" ,(mapconcat #'identity nrepl-javadoc-local-paths " "))
- (nrepl-make-response-handler
- (current-buffer)
- (lambda (buffer url)
- (if url
- (browse-url url)
- (error "No javadoc url for %s" symbol-name)))
- nil nil nil)))
-
-(defun nrepl-javadoc-handler (symbol-name)
- "Invoke the nREPL \"javadoc\" op on SYMBOL-NAME if available."
- (when symbol-name
- (let ((bounds (bounds-of-thing-at-point 'symbol)))
- (if (nrepl-op-supported-p "javadoc")
- (nrepl-javadoc-op symbol-name)
- (message "No Javadoc middleware available")))))
-
-(defun nrepl-javadoc (query)
- "Browse Javadoc on the Java class QUERY at point."
- (interactive "P")
- (nrepl-read-symbol-name "Javadoc for: " 'nrepl-javadoc-handler query))
-
;;; Response handlers
(defmacro nrepl-dbind-response (response keys &rest body)
"Destructure an nREPL RESPONSE dict.
@@ -699,390 +306,7 @@ DONE-HANDLER, and EVAL-ERROR-HANDLER as appropriate."
(if done-handler
(funcall done-handler buffer))))))))))
-(defun nrepl-stdin-handler (buffer)
- "Make a stdin response handler for BUFFER."
- (nrepl-make-response-handler buffer
- (lambda (buffer value)
- (nrepl-emit-result buffer value t))
- (lambda (buffer out)
- (nrepl-emit-output buffer out t))
- (lambda (buffer err)
- (nrepl-emit-output buffer err t))
- nil))
-
-(defun nrepl-handler (buffer)
- "Make a nrepl evaluation handler for BUFFER."
- (nrepl-make-response-handler buffer
- (lambda (buffer value)
- (nrepl-emit-result buffer value t))
- (lambda (buffer out)
- (nrepl-emit-output buffer out t))
- (lambda (buffer err)
- (nrepl-emit-output buffer err t))
- (lambda (buffer)
- (nrepl-emit-prompt buffer))))
-
-(defun nrepl-interactive-eval-handler (buffer)
- "Make an interactive eval handler for BUFFER."
- (nrepl-make-response-handler buffer
- (lambda (buffer value)
- (message "%s" value))
- (lambda (buffer value)
- (nrepl-emit-interactive-output value))
- (lambda (buffer err)
- (message "%s" err)
- (nrepl-highlight-compilation-errors
- buffer err))
- '()))
-
-(defun nrepl-load-file-handler (buffer)
- "Make a load file handler for BUFFER."
- (let (current-ns (nrepl-current-ns))
- (nrepl-make-response-handler buffer
- (lambda (buffer value)
- (message "%s" value)
- (with-current-buffer buffer
- (setq nrepl-buffer-ns (clojure-find-ns))
- (run-hooks 'nrepl-file-loaded-hook)))
- (lambda (buffer value)
- (nrepl-emit-interactive-output value))
- (lambda (buffer err)
- (message "%s" err)
- (nrepl-highlight-compilation-errors
- buffer err))
- '()
- (lambda (buffer ex root-ex session)
- (let ((nrepl-popup-on-error nil))
- (funcall nrepl-err-handler
- buffer ex root-ex session))))))
-
-(defun nrepl-interactive-eval-print-handler (buffer)
- "Make a handler for evaluating and printing result in BUFFER."
- (nrepl-make-response-handler buffer
- (lambda (buffer value)
- (with-current-buffer buffer
- (insert (format "%s" value))))
- '()
- (lambda (buffer err)
- (message "%s" err))
- '()))
-
-(defun nrepl-popup-eval-print-handler (buffer)
- "Make a handler for evaluating and printing result in popup BUFFER."
- (nrepl-make-response-handler buffer
- (lambda (buffer str)
- (nrepl-emit-into-popup-buffer buffer str))
- '()
- (lambda (buffer str)
- (nrepl-emit-into-popup-buffer buffer str))
- '()))
-
-(defun nrepl-popup-eval-out-handler (buffer)
- "Make a handler for evaluating and printing stdout/stderr in popup BUFFER."
- (nrepl-make-response-handler buffer
- '()
- (lambda (buffer str)
- (nrepl-emit-into-popup-buffer buffer str))
- (lambda (buffer str)
- (nrepl-emit-into-popup-buffer buffer str))
- '()))
-
-(defun nrepl-visit-error-buffer ()
- "Visit the `nrepl-error-buffer' (usually *nrepl-error*) if it exists."
- (interactive)
- (let ((buffer (get-buffer nrepl-error-buffer)))
- (when buffer
- (nrepl-popup-buffer-display buffer))))
-
-(defun nrepl-find-property (property &optional backward)
- "Find the next text region which has the specified PROPERTY.
-If BACKWARD is t, then search backward.
-Returns the position at which PROPERTY was found, or nil if not found."
- (let ((p (if backward
- (previous-single-char-property-change (point) property)
- (next-single-char-property-change (point) property))))
- (when (and (not (= p (point-min))) (not (= p (point-max))))
- p)))
-
-(defun nrepl-jump-to-compilation-error (&optional arg reset)
- "Jump to the line causing the current compilation error.
-
-ARG and RESET are ignored, as there is only ever one compilation error.
-They exist for compatibility with `next-error'."
- (interactive)
- (cl-labels ((goto-next-note-boundary
- ()
- (let ((p (or (nrepl-find-property 'nrepl-note-p)
- (nrepl-find-property 'nrepl-note-p t))))
- (when p
- (goto-char p)
- (message (get-char-property p 'nrepl-note))))))
- ;; if we're already on a compilation error, first jump to the end of
- ;; it, so that we find the next error.
- (when (get-char-property (point) 'nrepl-note-p)
- (goto-next-note-boundary))
- (goto-next-note-boundary)))
-
-(defun nrepl-default-err-handler (buffer ex root-ex session)
- "Make an error handler for BUFFER, EX, ROOT-EX and SESSION."
- ;; TODO: use ex and root-ex as fallback values to display when pst/print-stack-trace-not-found
- (let ((replp (equal 'nrepl-repl-mode (buffer-local-value 'major-mode buffer))))
- (if (or (and nrepl-popup-stacktraces-in-repl replp)
- (and nrepl-popup-stacktraces (not replp)))
- (lexical-let ((nrepl-popup-on-error nrepl-popup-on-error))
- (with-current-buffer buffer
- (nrepl-send-string "(if-let [pst+ (clojure.core/resolve 'clj-stacktrace.repl/pst+)]
- (pst+ *e) (clojure.stacktrace/print-stack-trace *e))"
- (nrepl-make-response-handler
- (nrepl-make-popup-buffer nrepl-error-buffer)
- nil
- (lambda (buffer value)
- (nrepl-emit-into-color-buffer buffer value)
- (when nrepl-popup-on-error
- (nrepl-popup-buffer-display buffer nrepl-auto-select-error-buffer)))
- nil nil) nil session))
- (with-current-buffer nrepl-error-buffer
- (compilation-minor-mode +1))))))
-
-(defvar nrepl-compilation-regexp
- '("\\(?:.*\\(warning, \\)\\|.*?\\(, compiling\\):(\\)\\([^:]*\\):\\([[:digit:]]+\\)\\(?::\\([[:digit:]]+\\)\\)?\\(\\(?: - \\(.*\\)\\)\\|)\\)" 3 4 5 (1))
- "Specifications for matching errors and warnings in Clojure stacktraces.
-See `compilation-error-regexp-alist' for help on their format.")
-
-(add-to-list 'compilation-error-regexp-alist-alist
- (cons 'nrepl nrepl-compilation-regexp))
-(add-to-list 'compilation-error-regexp-alist 'nrepl)
-
-(defun nrepl-extract-error-info (regexp message)
- "Extract error information with REGEXP against MESSAGE."
- (let ((file (nth 1 regexp))
- (line (nth 2 regexp))
- (col (nth 3 regexp))
- (type (nth 4 regexp))
- (pat (car regexp)))
- (when (string-match pat message)
- ;; special processing for type (1.2) style
- (setq type (if (consp type)
- (or (and (car type) (match-end (car type)) 1)
- (and (cdr type) (match-end (cdr type)) 0)
- 2)))
- (list
- (when file
- (let ((val (match-string-no-properties file message)))
- (unless (string= val "NO_SOURCE_PATH") val)))
- (when line (string-to-number (match-string-no-properties line message)))
- (when col
- (let ((val (match-string-no-properties col message)))
- (when val (string-to-number val))))
- (aref [nrepl-warning-highlight-face
- nrepl-warning-highlight-face
- nrepl-error-highlight-face]
- (or type 2))
- message))))
-
-(defun nrepl-highlight-compilation-errors (buffer message)
- "Highlight compilation error line in BUFFER, using MESSAGE."
- (with-current-buffer buffer
- (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message)))
- (when info
- (let ((file (nth 0 info))
- (line (nth 1 info))
- (col (nth 2 info))
- (face (nth 3 info))
- (note (nth 4 info)))
- (save-excursion
- ;; when we don't have a filename the line number
- ;; is relative to form start
- (if file
- (goto-char (point-min)) ; start of file
- (beginning-of-defun))
- (forward-line (1- line))
- ;; if have column, highlight sexp at that point otherwise whole line.
- (move-to-column (or col 0))
- (let ((begin (progn (if col (backward-up-list) (back-to-indentation)) (point)))
- (end (progn (if col (forward-sexp) (move-end-of-line nil)) (point))))
- (let ((overlay (make-overlay begin end)))
- (overlay-put overlay 'nrepl-note-p t)
- (overlay-put overlay 'face face)
- (overlay-put overlay 'nrepl-note note)
- (overlay-put overlay 'help-echo note)))))))))
-
-(defun nrepl-need-input (buffer)
- "Handle an need-input request from BUFFER."
- (with-current-buffer buffer
- (nrepl-send-stdin (concat (read-from-minibuffer "Stdin: ") "\n")
- (nrepl-stdin-handler buffer))))
-
-
-;;;; Popup buffers
-(define-minor-mode nrepl-popup-buffer-mode
- "Mode for nrepl popup buffers"
- nil
- (" nREPL-tmp")
- '(("q" . nrepl-popup-buffer-quit-function)))
-
-(make-variable-buffer-local
- (defvar nrepl-popup-buffer-quit-function 'nrepl-popup-buffer-quit
- "The function that is used to quit a temporary popup buffer."))
-
-(defun nrepl-popup-buffer-quit-function (&optional kill-buffer-p)
- "Wrapper to invoke the function `nrepl-popup-buffer-quit-function'.
-KILL-BUFFER-P is passed along."
- (interactive)
- (funcall nrepl-popup-buffer-quit-function kill-buffer-p))
-
-(defun nrepl-popup-buffer (name &optional select)
- "Create new popup buffer called NAME.
-If SELECT is non-nil, select the newly created window"
- (with-current-buffer (nrepl-make-popup-buffer name)
- (setq buffer-read-only t)
- (nrepl-popup-buffer-display (current-buffer) select)))
-
-(defun nrepl-popup-buffer-display (popup-buffer &optional select)
- "Display POPUP-BUFFER.
-If SELECT is non-nil, select the newly created window"
- (with-current-buffer popup-buffer
- (let ((new-window (display-buffer (current-buffer))))
- (set-window-point new-window (point))
- (when select
- (select-window new-window))
- (current-buffer))))
-
-(defun nrepl-popup-buffer-quit (&optional kill-buffer-p)
- "Quit the current (temp) window and bury its buffer using `quit-window'.
-If prefix argument KILL-BUFFER-P is non-nil, kill the buffer instead of burying it."
- (interactive)
- (quit-window kill-buffer-p (selected-window)))
-
-(defun nrepl-make-popup-buffer (name)
- "Create a temporary buffer called NAME."
- (with-current-buffer (get-buffer-create name)
- (kill-all-local-variables)
- (setq buffer-read-only nil)
- (erase-buffer)
- (set-syntax-table clojure-mode-syntax-table)
- (nrepl-popup-buffer-mode 1)
- (current-buffer)))
-
-(defun nrepl-emit-into-popup-buffer (buffer value)
- "Emit into BUFFER the provided VALUE."
- (with-current-buffer buffer
- (let ((inhibit-read-only t)
- (buffer-undo-list t))
- (insert (format "%s" value))
- (indent-sexp)
- (font-lock-fontify-buffer))))
-
-(defun nrepl-emit-into-color-buffer (buffer value)
- "Emit into color BUFFER the provided VALUE."
- (with-current-buffer buffer
- (let ((inhibit-read-only t)
- (buffer-undo-list t))
- (goto-char (point-max))
- (insert (format "%s" value))
- (ansi-color-apply-on-region (point-min) (point-max)))
- (goto-char (point-min))))
-
-
-(defun nrepl-popup-eval-print (form)
- "Evaluate the given FORM and print value in current buffer."
- (let ((buffer (current-buffer)))
- (nrepl-send-string form
- (nrepl-popup-eval-print-handler buffer)
- (nrepl-current-ns))))
-
-(defun nrepl-interactive-eval-print (form)
- "Evaluate the given FORM and print value in current buffer."
- (let ((buffer (current-buffer)))
- (nrepl-send-string form
- (nrepl-interactive-eval-print-handler buffer)
- (nrepl-current-ns))))
-
-(defun nrepl-interactive-eval (form)
- "Evaluate the given FORM and print value in minibuffer."
- (remove-overlays (point-min) (point-max) 'nrepl-note-p t)
- (let ((buffer (current-buffer)))
- (nrepl-send-string form
- (nrepl-interactive-eval-handler buffer)
- (nrepl-current-ns))))
-
-(defun nrepl-send-op (op attributes handler)
- "Send the specified OP with ATTRIBUTES and response HANDLER."
- (let ((buffer (current-buffer)))
- (nrepl-send-request (append
- (list "op" op
- "session" (nrepl-current-session)
- "ns" nrepl-buffer-ns)
- attributes)
- handler)))
-
-(defun nrepl-send-load-file (file-contents file-path file-name)
- "Perform the nREPL \"load-file\" op.
-FILE-CONTENTS, FILE-PATH and FILE-NAME are details of the file to be
-loaded."
- (let ((buffer (current-buffer)))
- (nrepl-send-request (list "op" "load-file"
- "session" (nrepl-current-session)
- "file" file-contents
- "file-path" file-path
- "file-name" file-name)
- (nrepl-load-file-handler buffer))))
-
-(defun nrepl-eval-last-expression (&optional prefix)
- "Evaluate the expression preceding point.
-If invoked with a PREFIX argument, print the result in the current buffer."
- (interactive "P")
- (if prefix
- (nrepl-interactive-eval-print (nrepl-last-expression))
- (nrepl-interactive-eval (nrepl-last-expression))))
-
-(defun nrepl-eval-print-last-expression ()
- "Evaluate the expression preceding point.
-Print its value into the current buffer"
- (interactive)
- (nrepl-interactive-eval-print (nrepl-last-expression)))
-
-(defun nrepl-pprint-eval-last-expression ()
- "Evaluate the expression preceding point and pprint its value in a popup buffer."
- (interactive)
- (let ((form (nrepl-last-expression))
- (result-buffer (nrepl-popup-buffer nrepl-result-buffer nil)))
- (nrepl-send-string (format "(clojure.pprint/pprint %s)" form)
- (nrepl-popup-eval-out-handler result-buffer)
- (nrepl-current-ns)
- (nrepl-current-tooling-session))))
-
-(defun clojure-enable-nrepl ()
- "Turn on nrepl interaction mode (see command `nrepl-interaction-mode').
-Useful in hooks."
- (nrepl-interaction-mode 1)
- (setq next-error-function 'nrepl-jump-to-compilation-error))
-
-(defun clojure-disable-nrepl ()
- "Turn off nrepl interaction mode (see command `nrepl-interaction-mode').
-Useful in hooks."
- (nrepl-interaction-mode -1))
-
;;; communication
-(defcustom nrepl-lein-command
- "lein"
- "The command used to execute leiningen 2.x."
- :type 'string
- :group 'nrepl-repl-mode)
-
-(defcustom nrepl-server-command
- (if (or (locate-file nrepl-lein-command exec-path)
- (locate-file (format "%s.bat" nrepl-lein-command) exec-path))
- (format "%s repl :headless" nrepl-lein-command)
- (format "echo \"%s repl :headless\" | eval $SHELL -l" nrepl-lein-command))
- "The command used to start the nREPL via command `nrepl-jack-in'.
-For a remote nREPL server lein must be in your PATH. The remote
-proc is launched via sh rather than bash, so it might be necessary
-to specific the full path to it. Localhost is assumed."
- :type 'string
- :group 'nrepl-repl-mode)
-
-
(defun nrepl-default-handler (response)
"Default handler which is invoked when no handler is found.
Handles message contained in RESPONSE."
@@ -1396,42 +620,6 @@ Refreshes EWOC."
(when buffer
(select-window (display-buffer buffer)))))
-(defun nrepl--clojure-version ()
- "Retrieve the underlying connection's Clojure version."
- (let ((version-string (plist-get (nrepl-send-string-sync "(clojure-version)") :value)))
- (substring version-string 1 (1- (length version-string)))))
-
-(defun nrepl--backend-version ()
- "Retrieve the underlying connection's nREPL version."
- (let ((version-string (plist-get (nrepl-send-string-sync "(:version-string clojure.tools.nrepl/version)") :value)))
- (substring version-string 1 (1- (length version-string)))))
-
-(defun nrepl--connection-info (nrepl-connection-buffer)
- "Return info about NREPL-CONNECTION-BUFFER.
-
-Info contains project name, current REPL namespace, host:port endpoint and Clojure version."
- (with-current-buffer (get-buffer nrepl-connection-buffer)
- (format "Active nrepl connection: %s:%s, %s:%s (Clojure %s, nREPL %s)"
- (or (nrepl--project-name nrepl-project-dir) "<no project>")
- nrepl-buffer-ns
- (car nrepl-endpoint)
- (cadr nrepl-endpoint)
- (nrepl--clojure-version)
- (nrepl--backend-version))))
-
-(defun nrepl-display-current-connection-info ()
- "Display information about the current connection."
- (interactive)
- (message (nrepl--connection-info (nrepl-current-connection-buffer))))
-
-(defun nrepl-rotate-connection ()
- "Rotate and display the current nrepl connection."
- (interactive)
- (setq nrepl-connection-list
- (append (cdr nrepl-connection-list)
- (list (car nrepl-connection-list))))
- (message (nrepl--connection-info (car nrepl-connection-list))))
-
;;; server messages
(defun nrepl-current-session ()
@@ -1535,9 +723,6 @@ See command `nrepl-eval-request' for details about how NS and SESSION
are processed."
(nrepl-send-request-sync (nrepl-eval-request input ns session)))
-(defalias 'nrepl-eval 'nrepl-send-string-sync)
-(defalias 'nrepl-eval-async 'nrepl-send-string)
-
(defun nrepl-find-ns ()
"Return the ns specified in the buffer, or \"user\" if no ns declaration is found."
(or (save-restriction
@@ -1554,97 +739,6 @@ search for and read a `ns' form."
(nrepl-find-ns))
ns)))
-(make-variable-buffer-local
- (defvar nrepl-last-clojure-buffer nil
- "A buffer-local variable holding the last Clojure source buffer.
-`nrepl-switch-to-last-clojure-buffer' uses this variable to jump
-back to last Clojure source buffer."))
-
-(defvar nrepl-current-clojure-buffer nil
- "This variable holds current buffer temporarily when connecting to a REPL.
-It is set to current buffer when `nrepl' or `nrepl-jack-in' is called.
-After the REPL buffer is created, the value of this variable is used
-to call `nrepl-remember-clojure-buffer'.")
-
-(defun nrepl-remember-clojure-buffer (buffer)
- "Try to remember the BUFFER from which the user jumps.
-The BUFFER needs to be a Clojure buffer and current major mode needs
-to be `nrepl-repl-mode'. The user can use `nrepl-switch-to-last-clojure-buffer'
-to jump back to the last Clojure source buffer."
- (when (and buffer
- (eq 'clojure-mode (with-current-buffer buffer major-mode))
- (eq 'nrepl-repl-mode major-mode))
- (setq nrepl-last-clojure-buffer buffer)))
-
-(defun nrepl-switch-to-repl-buffer (arg)
- "Select the REPL buffer, when possible in an existing window.
-
-Hint: You can use `display-buffer-reuse-frames' and
-`special-display-buffer-names' to customize the frame in which
-the buffer should appear.
-
-With a prefix ARG sets the name of the REPL buffer to the one
-of the current source file."
- (interactive "P")
- (if (not (get-buffer (nrepl-current-connection-buffer)))
- (message "No active nREPL connection.")
- (progn
- (let ((buffer (current-buffer)))
- (when arg
- (nrepl-set-ns (nrepl-current-ns)))
- (pop-to-buffer (nrepl-find-or-create-repl-buffer))
- (nrepl-remember-clojure-buffer buffer)
- (goto-char (point-max))))))
-
-(defun nrepl-switch-to-relevant-repl-buffer (arg)
- "Select the REPL buffer, when possible in an existing window.
-The buffer chosen is based on the file open in the current buffer.
-
-Hint: You can use `display-buffer-reuse-frames' and
-`special-display-buffer-names' to customize the frame in which
-the buffer should appear.
-
-With a prefix ARG sets the name of the REPL buffer to the one
-of the current source file.
-
-With a second prefix ARG the chosen REPL buffer is based on a
-supplied project directory."
- (interactive "P")
- (if (not (get-buffer (nrepl-current-connection-buffer)))
- (message "No active nREPL connection.")
- (progn
- (let ((project-directory
- (or (when arg
- (ido-read-directory-name "Project: "))
- (nrepl-project-directory-for (nrepl-current-dir)))))
- (if project-directory
- (let ((buf (car (-filter
- (lambda (conn)
- (let ((conn-proj-dir (with-current-buffer (get-buffer conn)
- nrepl-project-dir)))
- (when conn-proj-dir
- (equal (file-truename project-directory)
- (file-truename conn-proj-dir)))))
- nrepl-connection-list))))
- (if buf
- (setq nrepl-connection-list
- (cons buf (delq buf nrepl-connection-list)))
- (message "No relevant nREPL connection found. Switching to default connection.")))
- (message "No project directory found. Switching to default nREPL connection.")))
- (nrepl-switch-to-repl-buffer '()))))
-
-(defun nrepl-switch-to-last-clojure-buffer ()
- "Switch to the last Clojure buffer.
-The default keybinding for this command is
-the same as `nrepl-switch-to-repl-buffer',
-so that it is very convenient to jump between a
-Clojure buffer and the REPL buffer."
- (interactive)
- (if (and (eq 'nrepl-repl-mode major-mode)
- (buffer-live-p nrepl-last-clojure-buffer))
- (pop-to-buffer nrepl-last-clojure-buffer)
- (message "Don't know the original Clojure buffer")))
-
(defun nrepl-set-ns (ns)
"Switch the namespace of the REPL buffer to NS."
(interactive (list (nrepl-current-ns)))
@@ -1662,173 +756,6 @@ Clojure buffer and the REPL buffer."
(not (equal str ""))
(substring-no-properties str))))
-;; this is horrible, but with async callbacks we can't rely on dynamic scope
-(defvar nrepl-ido-ns nil)
-
-(defun nrepl-ido-form (ns)
- "Construct a Clojure form for ido read using NS."
- `(concat (if (find-ns (symbol ,ns))
- (map name (concat (keys (ns-interns (symbol ,ns)))
- (keys (ns-refers (symbol ,ns))))))
- (if (not= "" ,ns) [".."])
- (->> (all-ns)
- (map (fn [n]
- (re-find (re-pattern (str "^" (if (not= ,ns "")
- (str ,ns "\\."))
- "[^\\.]+"))
- (str n))))
- (filter identity)
- (map (fn [n] (str n "/")))
- (into (hash-set)))))
-
-(defun nrepl-ido-up-ns (ns)
- "Perform up using NS."
- (mapconcat 'identity (butlast (split-string ns "\\.")) "."))
-
-(defun nrepl-ido-select (selected targets callback)
- "Peform ido select using SELECTED, TARGETS and CALLBACK."
- ;; TODO: immediate RET gives "" as selected for some reason
- ;; this is an OK workaround though
- (cond ((equal "" selected)
- (nrepl-ido-select (car targets) targets callback))
- ((equal "/" (substring selected -1)) ; selected a namespace
- (nrepl-ido-read-var (substring selected 0 -1) callback))
- ((equal ".." selected)
- (nrepl-ido-read-var (nrepl-ido-up-ns nrepl-ido-ns) callback))
- ;; non ido variable selection techniques don't return qualified symbols, so this shouldn't either
- (t (funcall callback selected))))
-
-(defun nrepl-ido-read-var-handler (ido-callback buffer)
- "Create an ido read var handler with IDO-CALLBACK for BUFFER."
- (lexical-let ((ido-callback ido-callback))
- (nrepl-make-response-handler buffer
- (lambda (buffer value)
- ;; make sure to eval the callback in the buffer that the symbol was requested from so we get the right namespace
- (with-current-buffer buffer
- (let* ((targets (car (read-from-string value)))
- (selected (ido-completing-read "Var: " targets nil t)))
- (nrepl-ido-select selected targets ido-callback))))
- nil nil nil)))
-
-(defun nrepl-ido-read-var (ns ido-callback)
- "Perform ido read var in NS using IDO-CALLBACK."
- ;; Have to be stateful =(
- (setq nrepl-ido-ns ns)
- (nrepl-send-string (prin1-to-string (nrepl-ido-form nrepl-ido-ns))
- (nrepl-ido-read-var-handler ido-callback (current-buffer))
- nrepl-buffer-ns
- (nrepl-current-tooling-session)))
-
-(defun nrepl-read-symbol-name (prompt callback &optional query)
- "Either read a symbol name using PROMPT or choose the one at point.
-Use CALLBACK as the ido read var callback.
-The user is prompted with PROMPT if a prefix argument is in effect,
-if there is no symbol at point, or if QUERY is non-nil."
- (let ((symbol-name (nrepl-symbol-at-point)))
- (cond ((not (or current-prefix-arg query (not symbol-name)))
- (funcall callback symbol-name))
- (ido-mode (nrepl-ido-read-var nrepl-buffer-ns callback))
- (t (funcall callback (read-from-minibuffer prompt symbol-name))))))
-
-(defun nrepl-doc-handler (symbol)
- "Create a handler to lookup documentation for SYMBOL."
- (let ((form (format "(clojure.repl/doc %s)" symbol))
- (doc-buffer (nrepl-popup-buffer nrepl-doc-buffer t)))
- (nrepl-send-string form
- (nrepl-popup-eval-out-handler doc-buffer)
- nrepl-buffer-ns
- (nrepl-current-tooling-session))))
-
-(defun nrepl-doc (query)
- "Open a window with the docstring for the given QUERY.
-Defaults to the symbol at point. With prefix arg or no symbol
-under point, prompts for a var."
- (interactive "P")
- (nrepl-read-symbol-name "Symbol: " 'nrepl-doc-handler query))
-
-(defun nrepl-src-handler (symbol)
- "Create a handler to lookup source for SYMBOL."
- (let ((form (format "(clojure.repl/source %s)" symbol))
- (src-buffer (nrepl-popup-buffer nrepl-src-buffer t)))
- (with-current-buffer src-buffer
- (clojure-mode)
- (nrepl-popup-buffer-mode +1))
- (nrepl-send-string form
- (nrepl-popup-eval-out-handler src-buffer)
- nrepl-buffer-ns
- (nrepl-current-tooling-session))))
-
-(defun nrepl-src (query)
- "Open a window with the source for the given QUERY.
-Defaults to the symbol at point. With prefix arg or no symbol
-under point, prompts for a var."
- (interactive "P")
- (nrepl-read-symbol-name "Symbol: " 'nrepl-src-handler query))
-
-;; TODO: implement reloading ns
-(defun nrepl-eval-load-file (form)
- "Load FORM."
- (let ((buffer (current-buffer)))
- (nrepl-send-string form (nrepl-interactive-eval-handler buffer))))
-
-(defun nrepl-file-string (file)
- "Read the contents of a FILE and return as a string."
- (with-current-buffer (find-file-noselect file)
- (buffer-string)))
-
-(defun nrepl-load-file-op (filename)
- "Send \"load-file\" op for FILENAME."
- (nrepl-send-load-file (nrepl-file-string filename)
- filename
- (file-name-nondirectory filename)))
-
-(defun nrepl-load-file-core (filename)
- "Load the Clojure file FILENAME."
- (let ((fn (replace-regexp-in-string
- "\\\\" "\\\\\\\\"
- (convert-standard-filename (expand-file-name filename)))))
- (nrepl-eval-load-file
- (format "(clojure.core/load-file \"%s\")\n(in-ns '%s)\n"
- fn (nrepl-find-ns)))))
-
-(defun nrepl-dispatch-load-file (filename)
- "Dispatch the load file operation for FILENAME."
- (if (nrepl-op-supported-p "load-file")
- (nrepl-load-file-op filename)
- (nrepl-load-file-core filename)))
-
-(defun nrepl-load-file (filename)
- "Load the Clojure file FILENAME."
- (interactive (list
- (read-file-name "Load file: " nil nil
- nil (if (buffer-file-name)
- (file-name-nondirectory
- (buffer-file-name))))))
- (remove-overlays (point-min) (point-max) 'nrepl-note-p t)
- (nrepl-dispatch-load-file filename)
- (message "Loading %s..." filename))
-
-(defun nrepl-load-current-buffer ()
- "Load current buffer's file."
- (interactive)
- (check-parens)
- (unless buffer-file-name
- (error "Buffer %s is not associated with a file" (buffer-name)))
- (when (and (buffer-modified-p)
- (y-or-n-p (format "Save file %s? " (buffer-file-name))))
- (save-buffer))
- (nrepl-load-file (buffer-file-name)))
-
-(defun nrepl-recently-visited-buffer (mode)
- "Return the most recently visited buffer whose `major-mode' is MODE.
-Only considers buffers that are not already visible."
- (loop for buffer in (buffer-list)
- when (and (with-current-buffer buffer (eq major-mode mode))
- (not (string-match "^ " (buffer-name buffer)))
- (null (get-buffer-window buffer 'visible)))
- return buffer
- finally (error "Can't find unshown buffer in %S" mode)))
-
;;; interrupt
(defun nrepl-interrupt-handler (buffer)
"Create an interrupt response handler for BUFFER."
@@ -2002,7 +929,7 @@ If so ask the user for confirmation."
(defun nrepl-quit ()
"Quit the nrepl server."
(interactive)
- (when (y-or-n-p "Are you sure you want to quit nrepl?")
+ (when (y-or-n-p "Are you sure you want to quit nrepl? ")
(dolist (connection nrepl-connection-list)
(when connection
(nrepl-close connection)))
diff --git a/nrepl-eldoc.el b/nrepl-eldoc.el
new file mode 100644
index 00000000..54f96e5d
--- /dev/null
+++ b/nrepl-eldoc.el
@@ -0,0 +1,121 @@
+;;; nrepl-eldoc.el --- eldoc support for Clojure
+
+;; Copyright © 2012-2013 Tim King, Phil Hagelberg
+;; Copyright © 2013 Bozhidar Batsov, Hugo Duncan, Steve Purcell
+;;
+;; Author: Tim King <kingtim@gmail.com>
+;; Phil Hagelberg <technomancy@gmail.com>
+;; Bozhidar Batsov <bozhidar@batsov.com>
+;; Hugo Duncan <hugo@hugoduncan.org>
+;; Steve Purcell <steve@sanityinc.com>
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;; This file is not part of GNU Emacs.
+
+;;; Commentary:
+
+;; eldoc support for Clojure.
+
+;;; Code:
+
+(require 'nrepl-client)
+
+(require 'eldoc)
+(require 'dash)
+
+(defvar nrepl-extra-eldoc-commands '("nrepl-complete" "yas/expand")
+ "Extra commands to be added to eldoc's safe commands list.")
+
+(defun nrepl-eldoc-format-thing (thing)
+ "Format the eldoc THING."
+ (propertize thing 'face 'font-lock-function-name-face))
+
+(defun nrepl-highlight-args (arglist pos)
+ "Format the the function ARGLIST for eldoc.
+POS is the index of the currently highlighted argument."
+ (let* ((rest-pos (nrepl--find-rest-args-position arglist))
+ (i 0))
+ (mapconcat
+ (lambda (arg)
+ (let ((argstr (format "%s" arg)))
+ (if (eq arg '&)
+ argstr
+ (prog1
+ (if (or (= (1+ i) pos)
+ (and rest-pos (> (+ 1 i) rest-pos)
+ (> pos rest-pos)))
+ (propertize argstr 'face
+ 'eldoc-highlight-function-argument)
+ argstr)
+ (setq i (1+ i)))))) arglist " ")))
+
+(defun nrepl--find-rest-args-position (arglist)
+ "Find the position of & in the ARGLIST vector."
+ (-elem-index '& (append arglist ())))
+
+(defun nrepl-highlight-arglist (arglist pos)
+ "Format the ARGLIST for eldoc.
+POS is the index of the argument to highlight."
+ (concat "[" (nrepl-highlight-args arglist pos) "]"))
+
+(defun nrepl-eldoc-format-arglist (arglist pos)
+ "Format all the ARGLIST for eldoc.
+POS is the index of current argument."
+ (concat "("
+ (mapconcat (lambda (args) (nrepl-highlight-arglist args pos))
+ (read arglist) " ") ")"))
+
+(defun nrepl-eldoc-info-in-current-sexp ()
+ "Return a list of the current sexp and the current argument index."
+ (save-excursion
+ (let ((argument-index (1- (eldoc-beginning-of-sexp))))
+ ;; If we are at the beginning of function name, this will be -1.
+ (when (< argument-index 0)
+ (setq argument-index 0))
+ ;; Don't do anything if current word is inside a string.
+ (if (= (or (char-after (1- (point))) 0) ?\")
+ nil
+ (list (nrepl-symbol-at-point) argument-index)))))
+
+(defun nrepl-eldoc ()
+ "Backend function for eldoc to show argument list in the echo area."
+ (when (nrepl-current-connection-buffer)
+ (let* ((info (nrepl-eldoc-info-in-current-sexp))
+ (thing (car info))
+ (pos (cadr info))
+ (form (format "(try
+ (:arglists
+ (clojure.core/meta
+ (clojure.core/resolve
+ (clojure.core/read-string \"%s\"))))
+ (catch Throwable t nil))" thing))
+ (result (when thing
+ (nrepl-send-string-sync form
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session))))
+ (value (plist-get result :value)))
+ (unless (string= value "nil")
+ (format "%s: %s"
+ (nrepl-eldoc-format-thing thing)
+ (nrepl-eldoc-format-arglist value pos))))))
+
+(defun nrepl-turn-on-eldoc-mode ()
+ "Turn on eldoc mode in the current buffer."
+ (setq-local eldoc-documentation-function 'nrepl-eldoc)
+ (apply 'eldoc-add-command nrepl-extra-eldoc-commands)
+ (turn-on-eldoc-mode))
+
+(provide 'nrepl-eldoc)
+;;; nrepl-eldoc ends here
diff --git a/nrepl-interaction-mode.el b/nrepl-interaction-mode.el
index 1f4317f3..1e4c920e 100644
--- a/nrepl-interaction-mode.el
+++ b/nrepl-interaction-mode.el
@@ -30,6 +30,8 @@
;;; Code:
+(require 'nrepl-interaction)
+
(defvar nrepl-interaction-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "M-.") 'nrepl-jump)
diff --git a/nrepl-interaction.el b/nrepl-interaction.el
new file mode 100644
index 00000000..c9eaf5b1
--- /dev/null
+++ b/nrepl-interaction.el
@@ -0,0 +1,1035 @@
+;;; nrepl-interaction.el --- IDE for Clojure
+
+;; Copyright © 2012-2013 Tim King, Phil Hagelberg
+;; Copyright © 2013 Bozhidar Batsov, Hugo Duncan, Steve Purcell
+;;
+;; Author: Tim King <kingtim@gmail.com>
+;; Phil Hagelberg <technomancy@gmail.com>
+;; Bozhidar Batsov <bozhidar@batsov.com>
+;; Hugo Duncan <hugo@hugoduncan.org>
+;; Steve Purcell <steve@sanityinc.com>
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;; This file is not part of GNU Emacs.
+
+;;; Commentary:
+
+;; Provides an Emacs Lisp client to connect to Clojure nREPL servers.
+
+;;; Code:
+
+(require 'nrepl-client)
+
+(require 'clojure-mode)
+(require 'dash)
+(require 'thingatpt)
+(require 'etags)
+(require 'arc-mode)
+(require 'ansi-color)
+(require 'cl-lib)
+(require 'compile)
+(require 'tramp)
+
+(defconst nrepl-error-buffer "*nrepl-error*")
+(defconst nrepl-doc-buffer "*nrepl-doc*")
+(defconst nrepl-src-buffer "*nrepl-src*")
+(defconst nrepl-result-buffer "*nrepl-result*")
+
+(defcustom nrepl-use-local-resources t
+ "Use local resources under HOME if possible."
+ :type 'boolean
+ :group 'nrepl)
+
+(defcustom nrepl-popup-stacktraces t
+ "Non-nil means pop-up error stacktraces for evaluation errors.
+Nil means show only an error message in the minibuffer. See also
+`nrepl-popup-stacktraces-in-repl', which overrides this setting
+for REPL buffers."
+ :type 'boolean
+ :group 'nrepl)
+
+(defcustom nrepl-popup-on-error t
+ "When `nrepl-popup-on-error' is set to t, stacktraces will be displayed.
+When set to nil, stactraces will not be displayed, but will be available
+in the `nrepl-error-buffer', which defaults to *nrepl-error*."
+ :type 'boolean
+ :group 'nrepl)
+
+(defcustom nrepl-auto-select-error-buffer nil
+ "Controls whether to auto-select the error popup buffer."
+ :type 'boolean
+ :group 'nrepl)
+
+(defface nrepl-error-highlight-face
+ '((((supports :underline (:style wave)))
+ (:underline (:style wave :color "red") :inherit unspecified))
+ (t (:inherit font-lock-warning-face :underline t)))
+ "Face used to highlight compilation errors in Clojure buffers."
+ :group 'nrepl)
+
+(defface nrepl-warning-highlight-face
+ '((((supports :underline (:style wave)))
+ (:underline (:style wave :color "yellow") :inherit unspecified))
+ (t (:inherit font-lock-warning-face :underline (:color "yellow"))))
+ "Face used to highlight compilation warnings in Clojure buffers."
+ :group 'nrepl)
+
+;;; Connection info
+(defun nrepl--clojure-version ()
+ "Retrieve the underlying connection's Clojure version."
+ (let ((version-string (plist-get (nrepl-send-string-sync "(clojure-version)") :value)))
+ (substring version-string 1 (1- (length version-string)))))
+
+(defun nrepl--backend-version ()
+ "Retrieve the underlying connection's nREPL version."
+ (let ((version-string (plist-get (nrepl-send-string-sync "(:version-string clojure.tools.nrepl/version)") :value)))
+ (substring version-string 1 (1- (length version-string)))))
+
+(defun nrepl--connection-info (nrepl-connection-buffer)
+ "Return info about NREPL-CONNECTION-BUFFER.
+
+Info contains project name, current REPL namespace, host:port endpoint and Clojure version."
+ (with-current-buffer (get-buffer nrepl-connection-buffer)
+ (format "Active nrepl connection: %s:%s, %s:%s (Clojure %s, nREPL %s)"
+ (or (nrepl--project-name nrepl-project-dir) "<no project>")
+ nrepl-buffer-ns
+ (car nrepl-endpoint)
+ (cadr nrepl-endpoint)
+ (nrepl--clojure-version)
+ (nrepl--backend-version))))
+
+(defun nrepl-display-current-connection-info ()
+ "Display information about the current connection."
+ (interactive)
+ (message (nrepl--connection-info (nrepl-current-connection-buffer))))
+
+(defun nrepl-rotate-connection ()
+ "Rotate and display the current nrepl connection."
+ (interactive)
+ (setq nrepl-connection-list
+ (append (cdr nrepl-connection-list)
+ (list (car nrepl-connection-list))))
+ (message (nrepl--connection-info (car nrepl-connection-list))))
+
+;;; Switching between REPL & source buffers
+(make-variable-buffer-local
+ (defvar nrepl-last-clojure-buffer nil
+ "A buffer-local variable holding the last Clojure source buffer.
+`nrepl-switch-to-last-clojure-buffer' uses this variable to jump
+back to last Clojure source buffer."))
+
+(defvar nrepl-current-clojure-buffer nil
+ "This variable holds current buffer temporarily when connecting to a REPL.
+It is set to current buffer when `nrepl' or `nrepl-jack-in' is called.
+After the REPL buffer is created, the value of this variable is used
+to call `nrepl-remember-clojure-buffer'.")
+
+(defun nrepl-remember-clojure-buffer (buffer)
+ "Try to remember the BUFFER from which the user jumps.
+The BUFFER needs to be a Clojure buffer and current major mode needs
+to be `nrepl-repl-mode'. The user can use `nrepl-switch-to-last-clojure-buffer'
+to jump back to the last Clojure source buffer."
+ (when (and buffer
+ (eq 'clojure-mode (with-current-buffer buffer major-mode))
+ (eq 'nrepl-repl-mode major-mode))
+ (setq nrepl-last-clojure-buffer buffer)))
+
+(defun nrepl-switch-to-repl-buffer (arg)
+ "Select the REPL buffer, when possible in an existing window.
+
+Hint: You can use `display-buffer-reuse-frames' and
+`special-display-buffer-names' to customize the frame in which
+the buffer should appear.
+
+With a prefix ARG sets the name of the REPL buffer to the one
+of the current source file."
+ (interactive "P")
+ (if (not (get-buffer (nrepl-current-connection-buffer)))
+ (message "No active nREPL connection.")
+ (progn
+ (let ((buffer (current-buffer)))
+ (when arg
+ (nrepl-set-ns (nrepl-current-ns)))
+ (pop-to-buffer (nrepl-find-or-create-repl-buffer))
+ (nrepl-remember-clojure-buffer buffer)
+ (goto-char (point-max))))))
+
+(defun nrepl-switch-to-relevant-repl-buffer (arg)
+ "Select the REPL buffer, when possible in an existing window.
+The buffer chosen is based on the file open in the current buffer.
+
+Hint: You can use `display-buffer-reuse-frames' and
+`special-display-buffer-names' to customize the frame in which
+the buffer should appear.
+
+With a prefix ARG sets the name of the REPL buffer to the one
+of the current source file.
+
+With a second prefix ARG the chosen REPL buffer is based on a
+supplied project directory."
+ (interactive "P")
+ (if (not (get-buffer (nrepl-current-connection-buffer)))
+ (message "No active nREPL connection.")
+ (progn
+ (let ((project-directory
+ (or (when arg
+ (ido-read-directory-name "Project: "))
+ (nrepl-project-directory-for (nrepl-current-dir)))))
+ (if project-directory
+ (let ((buf (car (-filter
+ (lambda (conn)
+ (let ((conn-proj-dir (with-current-buffer (get-buffer conn)
+ nrepl-project-dir)))
+ (when conn-proj-dir
+ (equal (file-truename project-directory)
+ (file-truename conn-proj-dir)))))
+ nrepl-connection-list))))
+ (if buf
+ (setq nrepl-connection-list
+ (cons buf (delq buf nrepl-connection-list)))
+ (message "No relevant nREPL connection found. Switching to default connection.")))
+ (message "No project directory found. Switching to default nREPL connection.")))
+ (nrepl-switch-to-repl-buffer '()))))
+
+(defun nrepl-switch-to-last-clojure-buffer ()
+ "Switch to the last Clojure buffer.
+The default keybinding for this command is
+the same as `nrepl-switch-to-repl-buffer',
+so that it is very convenient to jump between a
+Clojure buffer and the REPL buffer."
+ (interactive)
+ (if (and (eq 'nrepl-repl-mode major-mode)
+ (buffer-live-p nrepl-last-clojure-buffer))
+ (pop-to-buffer nrepl-last-clojure-buffer)
+ (message "Don't know the original Clojure buffer")))
+
+;;; Evaluating
+(defun nrepl-eval-region (start end)
+ "Evaluate the region.
+The two arguments START and END are character positions;
+they can be in either order."
+ (interactive "r")
+ (nrepl-interactive-eval (buffer-substring-no-properties start end)))
+
+(defun nrepl-eval-buffer ()
+ "Evaluate the current buffer."
+ (interactive)
+ (nrepl-eval-region (point-min) (point-max)))
+
+(defun nrepl-expression-at-point ()
+ "Return the text of the expr at point."
+ (apply #'buffer-substring-no-properties
+ (nrepl-region-for-expression-at-point)))
+
+(defun nrepl-region-for-expression-at-point ()
+ "Return the start and end position of defun at point."
+ (save-excursion
+ (save-match-data
+ (end-of-defun)
+ (let ((end (point)))
+ (beginning-of-defun)
+ (list (point) end)))))
+
+(defun nrepl-eval-expression-at-point (&optional prefix)
+ "Evaluate the current toplevel form, and print result in the mini-buffer.
+With a PREFIX argument, print the result in the current buffer."
+ (interactive "P")
+ (let ((form (nrepl-expression-at-point)))
+ (if prefix
+ (nrepl-interactive-eval-print form)
+ (nrepl-interactive-eval form))))
+
+(defun nrepl-eval-ns-form ()
+ "Evaluate the current buffer's namespace form."
+ (interactive)
+ (when (clojure-find-ns)
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (nrepl-eval-expression-at-point))))
+
+(defun nrepl-bounds-of-sexp-at-point ()
+ "Return the bounds sexp at point as a pair (or nil)."
+ (or (and (equal (char-after) ?\()
+ (member (char-before) '(?\' ?\, ?\@))
+ ;; hide stuff before ( to avoid quirks with '( etc.
+ (save-restriction
+ (narrow-to-region (point) (point-max))
+ (bounds-of-thing-at-point 'sexp)))
+ (bounds-of-thing-at-point 'sexp)))
+
+(defun nrepl-sexp-at-point ()
+ "Return the sexp at point as a string, otherwise nil."
+ (let ((bounds (nrepl-bounds-of-sexp-at-point)))
+ (if bounds
+ (buffer-substring-no-properties (car bounds)
+ (cdr bounds)))))
+
+(defun nrepl-sexp-at-point-with-bounds ()
+ "Return a list containing the sexp at point and its bounds."
+ (let ((bounds (nrepl-bounds-of-sexp-at-point)))
+ (if bounds
+ (let ((start (car bounds))
+ (end (cdr bounds)))
+ (list (buffer-substring-no-properties start end)
+ (cons (set-marker (make-marker) start)
+ (set-marker (make-marker) end)))))))
+
+(defun nrepl-last-expression ()
+ "Return the last sexp."
+ (buffer-substring-no-properties
+ (save-excursion (backward-sexp) (point))
+ (point)))
+
+;;;
+(defun nrepl-tramp-prefix ()
+ "Top element on `find-tag-marker-ring` used to determine Clojure host."
+ (let ((jump-origin (buffer-file-name
+ (marker-buffer
+ (ring-ref find-tag-marker-ring 0)))))
+ (when (tramp-tramp-file-p jump-origin)
+ (let ((vec (tramp-dissect-file-name jump-origin)))
+ (tramp-make-tramp-file-name (tramp-file-name-method vec)
+ (tramp-file-name-user vec)
+ (tramp-file-name-host vec)
+ nil)))))
+
+(defun nrepl-home-prefix-adjustment (resource)
+ "System-dependent HOME location will be adjusted in RESOURCE.
+Removes any leading slash if on Windows."
+ (save-match-data
+ (cond ((string-match "^\\/\\(Users\\|home\\)\\/\\w+\\(\\/.+\\)" resource)
+ (concat (getenv "HOME") (match-string 2 resource)))
+ ((and (eq system-type 'windows-nt)
+ (string-match "^/" resource)
+ (not (tramp-tramp-file-p resource)))
+ (substring resource 1))
+ (t
+ resource))))
+
+(defun nrepl-emacs-or-clojure-side-adjustment (resource)
+ "Fix the RESOURCE path depending on `nrepl-use-local-resources`."
+ (let ((resource (nrepl-home-prefix-adjustment resource))
+ (clojure-side-res (concat (nrepl-tramp-prefix) resource))
+ (emacs-side-res resource))
+ (cond ((equal resource "") resource)
+ ((and nrepl-use-local-resources
+ (file-exists-p emacs-side-res))
+ emacs-side-res)
+ ((file-exists-p clojure-side-res)
+ clojure-side-res)
+ (t
+ resource))))
+
+(defun nrepl-find-file (filename)
+ "Switch to a buffer visiting FILENAME.
+Adjusts for HOME location using `nrepl-home-prefix-adjustment'. Uses `find-file'."
+ (find-file (nrepl-emacs-or-clojure-side-adjustment filename)))
+
+(defun nrepl-find-resource (resource)
+ "Find and display RESOURCE."
+ (cond ((string-match "^file:\\(.+\\)" resource)
+ (nrepl-find-file (match-string 1 resource)))
+ ((string-match "^\\(jar\\|zip\\):file:\\(.+\\)!/\\(.+\\)" resource)
+ (let* ((jar (match-string 2 resource))
+ (path (match-string 3 resource))
+ (buffer-already-open (get-buffer (file-name-nondirectory jar))))
+ (nrepl-find-file jar)
+ (goto-char (point-min))
+ (search-forward path)
+ (let ((opened-buffer (current-buffer)))
+ (archive-extract)
+ (when (not buffer-already-open)
+ (kill-buffer opened-buffer)))))
+ (t (error "Unknown resource path %s" resource))))
+
+(defun nrepl-jump-to-def-for (location)
+ "Jump to LOCATION's definition in the source code."
+ ;; ugh; elisp destructuring doesn't work for vectors
+ (let ((resource (aref location 0))
+ (path (aref location 1))
+ (line (aref location 2)))
+ (if (and path (file-exists-p path))
+ (find-file path)
+ (nrepl-find-resource resource))
+ (goto-char (point-min))
+ (forward-line (1- line))))
+
+(defun nrepl-jump-to-def-handler (buffer)
+ "Create a handler for jump-to-def in BUFFER."
+ ;; TODO: got to be a simpler way to do this
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (with-current-buffer buffer
+ (ring-insert find-tag-marker-ring (point-marker)))
+ (nrepl-jump-to-def-for
+ (car (read-from-string value))))
+ (lambda (buffer out) (message out))
+ (lambda (buffer err) (message err))
+ nil))
+
+(defun nrepl-jump-to-def (var)
+ "Jump to the definition of the VAR at point."
+ (let ((form (format "(let [ns-symbol '%s
+ ns-var '%s
+ ns-file (clojure.core/comp :file
+ clojure.core/meta
+ clojure.core/second
+ clojure.core/first
+ clojure.core/ns-publics)
+ resource-str (clojure.core/comp clojure.core/str
+ clojure.java.io/resource
+ ns-file)
+ file-str (clojure.core/comp clojure.core/str
+ clojure.java.io/file
+ ns-file)]
+ (cond ((clojure.core/ns-aliases ns-symbol) ns-var)
+ (let [resolved-ns ((clojure.core/ns-aliases ns-symbol) ns-var)]
+ [(resource-str resolved-ns)
+ (file-str resolved-ns)
+ 1])
+
+ (find-ns ns-var)
+ [(resource-str ns-var)
+ (file-str ns-var)
+ 1]
+
+ (clojure.core/ns-resolve ns-symbol ns-var)
+ ((clojure.core/juxt
+ (clojure.core/comp clojure.core/str
+ clojure.java.io/resource
+ :file)
+ (clojure.core/comp clojure.core/str
+ clojure.java.io/file
+ :file)
+ :line)
+ (clojure.core/meta (clojure.core/ns-resolve ns-symbol ns-var)))))"
+ (nrepl-current-ns) var)))
+ (nrepl-send-string form
+ (nrepl-jump-to-def-handler (current-buffer))
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session))))
+
+(defun nrepl-jump (query)
+ "Jump to the definition of QUERY."
+ (interactive "P")
+ (nrepl-read-symbol-name "Symbol: " 'nrepl-jump-to-def query))
+
+(defalias 'nrepl-jump-back 'pop-tag-mark)
+
+(defun nrepl-completion-complete-core-fn (str)
+ "Return a list of completions for STR using complete.core/completions."
+ (let ((strlst (plist-get
+ (nrepl-send-string-sync
+ (format "(require 'complete.core) (complete.core/completions \"%s\" *ns*)" str)
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session))
+ :value)))
+ (when strlst
+ (car (read-from-string strlst)))))
+
+(defun nrepl-completion-complete-op-fn (str)
+ "Return a list of completions for STR using the nREPL \"complete\" op."
+ (lexical-let ((strlst (plist-get
+ (nrepl-send-request-sync
+ (list "op" "complete"
+ "session" (nrepl-current-tooling-session)
+ "ns" nrepl-buffer-ns
+ "symbol" str))
+ :value)))
+ (when strlst
+ (car strlst))))
+
+(defun nrepl-dispatch-complete-symbol (str)
+ "Return a list of completions for STR.
+Dispatch to the nREPL \"complete\" op if supported,
+otherwise dispatch to internal completion function."
+ (if (nrepl-op-supported-p "complete")
+ (nrepl-completion-complete-op-fn str)
+ (nrepl-completion-complete-core-fn str)))
+
+(defun nrepl-complete-at-point ()
+ "Complete the symbol at point."
+ (let ((sap (symbol-at-point)))
+ (when (and sap (not (in-string-p)))
+ (let ((bounds (bounds-of-thing-at-point 'symbol)))
+ (list (car bounds) (cdr bounds)
+ (completion-table-dynamic #'nrepl-dispatch-complete-symbol))))))
+
+
+;;; JavaDoc Browsing
+;;; Assumes local-paths are accessible in the VM.
+(defvar nrepl-javadoc-local-paths nil
+ "List of paths to directories with Javadoc.")
+
+(defun nrepl-javadoc-op (symbol-name)
+ "Invoke the nREPL \"javadoc\" op on SYMBOL-NAME."
+ (nrepl-send-op
+ "javadoc"
+ `("symbol" ,symbol-name "ns" ,nrepl-buffer-ns
+ "local-paths" ,(mapconcat #'identity nrepl-javadoc-local-paths " "))
+ (nrepl-make-response-handler
+ (current-buffer)
+ (lambda (buffer url)
+ (if url
+ (browse-url url)
+ (error "No javadoc url for %s" symbol-name)))
+ nil nil nil)))
+
+(defun nrepl-javadoc-handler (symbol-name)
+ "Invoke the nREPL \"javadoc\" op on SYMBOL-NAME if available."
+ (when symbol-name
+ (let ((bounds (bounds-of-thing-at-point 'symbol)))
+ (if (nrepl-op-supported-p "javadoc")
+ (nrepl-javadoc-op symbol-name)
+ (message "No Javadoc middleware available")))))
+
+(defun nrepl-javadoc (query)
+ "Browse Javadoc on the Java class QUERY at point."
+ (interactive "P")
+ (nrepl-read-symbol-name "Javadoc for: " 'nrepl-javadoc-handler query))
+
+(defun nrepl-stdin-handler (buffer)
+ "Make a stdin response handler for BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (nrepl-emit-result buffer value t))
+ (lambda (buffer out)
+ (nrepl-emit-output buffer out t))
+ (lambda (buffer err)
+ (nrepl-emit-output buffer err t))
+ nil))
+
+(defun nrepl-handler (buffer)
+ "Make a nrepl evaluation handler for BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (nrepl-emit-result buffer value t))
+ (lambda (buffer out)
+ (nrepl-emit-output buffer out t))
+ (lambda (buffer err)
+ (nrepl-emit-output buffer err t))
+ (lambda (buffer)
+ (nrepl-emit-prompt buffer))))
+
+(defun nrepl-interactive-eval-handler (buffer)
+ "Make an interactive eval handler for BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (message "%s" value))
+ (lambda (buffer value)
+ (nrepl-emit-interactive-output value))
+ (lambda (buffer err)
+ (message "%s" err)
+ (nrepl-highlight-compilation-errors
+ buffer err))
+ '()))
+
+(defun nrepl-load-file-handler (buffer)
+ "Make a load file handler for BUFFER."
+ (let (current-ns (nrepl-current-ns))
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (message "%s" value)
+ (with-current-buffer buffer
+ (setq nrepl-buffer-ns (clojure-find-ns))
+ (run-hooks 'nrepl-file-loaded-hook)))
+ (lambda (buffer value)
+ (nrepl-emit-interactive-output value))
+ (lambda (buffer err)
+ (message "%s" err)
+ (nrepl-highlight-compilation-errors
+ buffer err))
+ '()
+ (lambda (buffer ex root-ex session)
+ (let ((nrepl-popup-on-error nil))
+ (funcall nrepl-err-handler
+ buffer ex root-ex session))))))
+
+(defun nrepl-interactive-eval-print-handler (buffer)
+ "Make a handler for evaluating and printing result in BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ (with-current-buffer buffer
+ (insert (format "%s" value))))
+ '()
+ (lambda (buffer err)
+ (message "%s" err))
+ '()))
+
+(defun nrepl-popup-eval-print-handler (buffer)
+ "Make a handler for evaluating and printing result in popup BUFFER."
+ (nrepl-make-response-handler buffer
+ (lambda (buffer str)
+ (nrepl-emit-into-popup-buffer buffer str))
+ '()
+ (lambda (buffer str)
+ (nrepl-emit-into-popup-buffer buffer str))
+ '()))
+
+(defun nrepl-popup-eval-out-handler (buffer)
+ "Make a handler for evaluating and printing stdout/stderr in popup BUFFER."
+ (nrepl-make-response-handler buffer
+ '()
+ (lambda (buffer str)
+ (nrepl-emit-into-popup-buffer buffer str))
+ (lambda (buffer str)
+ (nrepl-emit-into-popup-buffer buffer str))
+ '()))
+
+(defun nrepl-visit-error-buffer ()
+ "Visit the `nrepl-error-buffer' (usually *nrepl-error*) if it exists."
+ (interactive)
+ (let ((buffer (get-buffer nrepl-error-buffer)))
+ (when buffer
+ (nrepl-popup-buffer-display buffer))))
+
+(defun nrepl-find-property (property &optional backward)
+ "Find the next text region which has the specified PROPERTY.
+If BACKWARD is t, then search backward.
+Returns the position at which PROPERTY was found, or nil if not found."
+ (let ((p (if backward
+ (previous-single-char-property-change (point) property)
+ (next-single-char-property-change (point) property))))
+ (when (and (not (= p (point-min))) (not (= p (point-max))))
+ p)))
+
+(defun nrepl-jump-to-compilation-error (&optional arg reset)
+ "Jump to the line causing the current compilation error.
+
+ARG and RESET are ignored, as there is only ever one compilation error.
+They exist for compatibility with `next-error'."
+ (interactive)
+ (cl-labels ((goto-next-note-boundary
+ ()
+ (let ((p (or (nrepl-find-property 'nrepl-note-p)
+ (nrepl-find-property 'nrepl-note-p t))))
+ (when p
+ (goto-char p)
+ (message (get-char-property p 'nrepl-note))))))
+ ;; if we're already on a compilation error, first jump to the end of
+ ;; it, so that we find the next error.
+ (when (get-char-property (point) 'nrepl-note-p)
+ (goto-next-note-boundary))
+ (goto-next-note-boundary)))
+
+(defun nrepl-default-err-handler (buffer ex root-ex session)
+ "Make an error handler for BUFFER, EX, ROOT-EX and SESSION."
+ ;; TODO: use ex and root-ex as fallback values to display when pst/print-stack-trace-not-found
+ (let ((replp (equal 'nrepl-repl-mode (buffer-local-value 'major-mode buffer))))
+ (if (or (and nrepl-popup-stacktraces-in-repl replp)
+ (and nrepl-popup-stacktraces (not replp)))
+ (lexical-let ((nrepl-popup-on-error nrepl-popup-on-error))
+ (with-current-buffer buffer
+ (nrepl-send-string "(if-let [pst+ (clojure.core/resolve 'clj-stacktrace.repl/pst+)]
+ (pst+ *e) (clojure.stacktrace/print-stack-trace *e))"
+ (nrepl-make-response-handler
+ (nrepl-make-popup-buffer nrepl-error-buffer)
+ nil
+ (lambda (buffer value)
+ (nrepl-emit-into-color-buffer buffer value)
+ (when nrepl-popup-on-error
+ (nrepl-popup-buffer-display buffer nrepl-auto-select-error-buffer)))
+ nil nil) nil session))
+ (with-current-buffer nrepl-error-buffer
+ (compilation-minor-mode +1))))))
+
+(defvar nrepl-compilation-regexp
+ '("\\(?:.*\\(warning, \\)\\|.*?\\(, compiling\\):(\\)\\([^:]*\\):\\([[:digit:]]+\\)\\(?::\\([[:digit:]]+\\)\\)?\\(\\(?: - \\(.*\\)\\)\\|)\\)" 3 4 5 (1))
+ "Specifications for matching errors and warnings in Clojure stacktraces.
+See `compilation-error-regexp-alist' for help on their format.")
+
+(add-to-list 'compilation-error-regexp-alist-alist
+ (cons 'nrepl nrepl-compilation-regexp))
+(add-to-list 'compilation-error-regexp-alist 'nrepl)
+
+(defun nrepl-extract-error-info (regexp message)
+ "Extract error information with REGEXP against MESSAGE."
+ (let ((file (nth 1 regexp))
+ (line (nth 2 regexp))
+ (col (nth 3 regexp))
+ (type (nth 4 regexp))
+ (pat (car regexp)))
+ (when (string-match pat message)
+ ;; special processing for type (1.2) style
+ (setq type (if (consp type)
+ (or (and (car type) (match-end (car type)) 1)
+ (and (cdr type) (match-end (cdr type)) 0)
+ 2)))
+ (list
+ (when file
+ (let ((val (match-string-no-properties file message)))
+ (unless (string= val "NO_SOURCE_PATH") val)))
+ (when line (string-to-number (match-string-no-properties line message)))
+ (when col
+ (let ((val (match-string-no-properties col message)))
+ (when val (string-to-number val))))
+ (aref [nrepl-warning-highlight-face
+ nrepl-warning-highlight-face
+ nrepl-error-highlight-face]
+ (or type 2))
+ message))))
+
+(defun nrepl-highlight-compilation-errors (buffer message)
+ "Highlight compilation error line in BUFFER, using MESSAGE."
+ (with-current-buffer buffer
+ (let ((info (nrepl-extract-error-info nrepl-compilation-regexp message)))
+ (when info
+ (let ((file (nth 0 info))
+ (line (nth 1 info))
+ (col (nth 2 info))
+ (face (nth 3 info))
+ (note (nth 4 info)))
+ (save-excursion
+ ;; when we don't have a filename the line number
+ ;; is relative to form start
+ (if file
+ (goto-char (point-min)) ; start of file
+ (beginning-of-defun))
+ (forward-line (1- line))
+ ;; if have column, highlight sexp at that point otherwise whole line.
+ (move-to-column (or col 0))
+ (let ((begin (progn (if col (backward-up-list) (back-to-indentation)) (point)))
+ (end (progn (if col (forward-sexp) (move-end-of-line nil)) (point))))
+ (let ((overlay (make-overlay begin end)))
+ (overlay-put overlay 'nrepl-note-p t)
+ (overlay-put overlay 'face face)
+ (overlay-put overlay 'nrepl-note note)
+ (overlay-put overlay 'help-echo note)))))))))
+
+(defun nrepl-need-input (buffer)
+ "Handle an need-input request from BUFFER."
+ (with-current-buffer buffer
+ (nrepl-send-stdin (concat (read-from-minibuffer "Stdin: ") "\n")
+ (nrepl-stdin-handler buffer))))
+
+
+;;;; Popup buffers
+(define-minor-mode nrepl-popup-buffer-mode
+ "Mode for nrepl popup buffers"
+ nil
+ (" nREPL-tmp")
+ '(("q" . nrepl-popup-buffer-quit-function)))
+
+(make-variable-buffer-local
+ (defvar nrepl-popup-buffer-quit-function 'nrepl-popup-buffer-quit
+ "The function that is used to quit a temporary popup buffer."))
+
+(defun nrepl-popup-buffer-quit-function (&optional kill-buffer-p)
+ "Wrapper to invoke the function `nrepl-popup-buffer-quit-function'.
+KILL-BUFFER-P is passed along."
+ (interactive)
+ (funcall nrepl-popup-buffer-quit-function kill-buffer-p))
+
+(defun nrepl-popup-buffer (name &optional select)
+ "Create new popup buffer called NAME.
+If SELECT is non-nil, select the newly created window"
+ (with-current-buffer (nrepl-make-popup-buffer name)
+ (setq buffer-read-only t)
+ (nrepl-popup-buffer-display (current-buffer) select)))
+
+(defun nrepl-popup-buffer-display (popup-buffer &optional select)
+ "Display POPUP-BUFFER.
+If SELECT is non-nil, select the newly created window"
+ (with-current-buffer popup-buffer
+ (let ((new-window (display-buffer (current-buffer))))
+ (set-window-point new-window (point))
+ (when select
+ (select-window new-window))
+ (current-buffer))))
+
+(defun nrepl-popup-buffer-quit (&optional kill-buffer-p)
+ "Quit the current (temp) window and bury its buffer using `quit-window'.
+If prefix argument KILL-BUFFER-P is non-nil, kill the buffer instead of burying it."
+ (interactive)
+ (quit-window kill-buffer-p (selected-window)))
+
+(defun nrepl-make-popup-buffer (name)
+ "Create a temporary buffer called NAME."
+ (with-current-buffer (get-buffer-create name)
+ (kill-all-local-variables)
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (set-syntax-table clojure-mode-syntax-table)
+ (nrepl-popup-buffer-mode 1)
+ (current-buffer)))
+
+(defun nrepl-emit-into-popup-buffer (buffer value)
+ "Emit into BUFFER the provided VALUE."
+ (with-current-buffer buffer
+ (let ((inhibit-read-only t)
+ (buffer-undo-list t))
+ (insert (format "%s" value))
+ (indent-sexp)
+ (font-lock-fontify-buffer))))
+
+(defun nrepl-emit-into-color-buffer (buffer value)
+ "Emit into color BUFFER the provided VALUE."
+ (with-current-buffer buffer
+ (let ((inhibit-read-only t)
+ (buffer-undo-list t))
+ (goto-char (point-max))
+ (insert (format "%s" value))
+ (ansi-color-apply-on-region (point-min) (point-max)))
+ (goto-char (point-min))))
+
+
+(defun nrepl-popup-eval-print (form)
+ "Evaluate the given FORM and print value in current buffer."
+ (let ((buffer (current-buffer)))
+ (nrepl-send-string form
+ (nrepl-popup-eval-print-handler buffer)
+ (nrepl-current-ns))))
+
+(defun nrepl-interactive-eval-print (form)
+ "Evaluate the given FORM and print value in current buffer."
+ (let ((buffer (current-buffer)))
+ (nrepl-send-string form
+ (nrepl-interactive-eval-print-handler buffer)
+ (nrepl-current-ns))))
+
+(defun nrepl-interactive-eval (form)
+ "Evaluate the given FORM and print value in minibuffer."
+ (remove-overlays (point-min) (point-max) 'nrepl-note-p t)
+ (let ((buffer (current-buffer)))
+ (nrepl-send-string form
+ (nrepl-interactive-eval-handler buffer)
+ (nrepl-current-ns))))
+
+(defun nrepl-send-op (op attributes handler)
+ "Send the specified OP with ATTRIBUTES and response HANDLER."
+ (let ((buffer (current-buffer)))
+ (nrepl-send-request (append
+ (list "op" op
+ "session" (nrepl-current-session)
+ "ns" nrepl-buffer-ns)
+ attributes)
+ handler)))
+
+(defun nrepl-send-load-file (file-contents file-path file-name)
+ "Perform the nREPL \"load-file\" op.
+FILE-CONTENTS, FILE-PATH and FILE-NAME are details of the file to be
+loaded."
+ (let ((buffer (current-buffer)))
+ (nrepl-send-request (list "op" "load-file"
+ "session" (nrepl-current-session)
+ "file" file-contents
+ "file-path" file-path
+ "file-name" file-name)
+ (nrepl-load-file-handler buffer))))
+
+(defun nrepl-eval-last-expression (&optional prefix)
+ "Evaluate the expression preceding point.
+If invoked with a PREFIX argument, print the result in the current buffer."
+ (interactive "P")
+ (if prefix
+ (nrepl-interactive-eval-print (nrepl-last-expression))
+ (nrepl-interactive-eval (nrepl-last-expression))))
+
+(defun nrepl-eval-print-last-expression ()
+ "Evaluate the expression preceding point.
+Print its value into the current buffer"
+ (interactive)
+ (nrepl-interactive-eval-print (nrepl-last-expression)))
+
+(defun nrepl-pprint-eval-last-expression ()
+ "Evaluate the expression preceding point and pprint its value in a popup buffer."
+ (interactive)
+ (let ((form (nrepl-last-expression))
+ (result-buffer (nrepl-popup-buffer nrepl-result-buffer nil)))
+ (nrepl-send-string (format "(clojure.pprint/pprint %s)" form)
+ (nrepl-popup-eval-out-handler result-buffer)
+ (nrepl-current-ns)
+ (nrepl-current-tooling-session))))
+
+(defun clojure-enable-nrepl ()
+ "Turn on nrepl interaction mode (see command `nrepl-interaction-mode').
+Useful in hooks."
+ (nrepl-interaction-mode 1)
+ (setq next-error-function 'nrepl-jump-to-compilation-error))
+
+(defun clojure-disable-nrepl ()
+ "Turn off nrepl interaction mode (see command `nrepl-interaction-mode').
+Useful in hooks."
+ (nrepl-interaction-mode -1))
+
+;; this is horrible, but with async callbacks we can't rely on dynamic scope
+(defvar nrepl-ido-ns nil)
+
+(defun nrepl-ido-form (ns)
+ "Construct a Clojure form for ido read using NS."
+ `(concat (if (find-ns (symbol ,ns))
+ (map name (concat (keys (ns-interns (symbol ,ns)))
+ (keys (ns-refers (symbol ,ns))))))
+ (if (not= "" ,ns) [".."])
+ (->> (all-ns)
+ (map (fn [n]
+ (re-find (re-pattern (str "^" (if (not= ,ns "")
+ (str ,ns "\\."))
+ "[^\\.]+"))
+ (str n))))
+ (filter identity)
+ (map (fn [n] (str n "/")))
+ (into (hash-set)))))
+
+(defun nrepl-ido-up-ns (ns)
+ "Perform up using NS."
+ (mapconcat 'identity (butlast (split-string ns "\\.")) "."))
+
+(defun nrepl-ido-select (selected targets callback)
+ "Peform ido select using SELECTED, TARGETS and CALLBACK."
+ ;; TODO: immediate RET gives "" as selected for some reason
+ ;; this is an OK workaround though
+ (cond ((equal "" selected)
+ (nrepl-ido-select (car targets) targets callback))
+ ((equal "/" (substring selected -1)) ; selected a namespace
+ (nrepl-ido-read-var (substring selected 0 -1) callback))
+ ((equal ".." selected)
+ (nrepl-ido-read-var (nrepl-ido-up-ns nrepl-ido-ns) callback))
+ ;; non ido variable selection techniques don't return qualified symbols, so this shouldn't either
+ (t (funcall callback selected))))
+
+(defun nrepl-ido-read-var-handler (ido-callback buffer)
+ "Create an ido read var handler with IDO-CALLBACK for BUFFER."
+ (lexical-let ((ido-callback ido-callback))
+ (nrepl-make-response-handler buffer
+ (lambda (buffer value)
+ ;; make sure to eval the callback in the buffer that the symbol was requested from so we get the right namespace
+ (with-current-buffer buffer
+ (let* ((targets (car (read-from-string value)))
+ (selected (ido-completing-read "Var: " targets nil t)))
+ (nrepl-ido-select selected targets ido-callback))))
+ nil nil nil)))
+
+(defun nrepl-ido-read-var (ns ido-callback)
+ "Perform ido read var in NS using IDO-CALLBACK."
+ ;; Have to be stateful =(
+ (setq nrepl-ido-ns ns)
+ (nrepl-send-string (prin1-to-string (nrepl-ido-form nrepl-ido-ns))
+ (nrepl-ido-read-var-handler ido-callback (current-buffer))
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session)))
+
+(defun nrepl-read-symbol-name (prompt callback &optional query)
+ "Either read a symbol name using PROMPT or choose the one at point.
+Use CALLBACK as the ido read var callback.
+The user is prompted with PROMPT if a prefix argument is in effect,
+if there is no symbol at point, or if QUERY is non-nil."
+ (let ((symbol-name (nrepl-symbol-at-point)))
+ (cond ((not (or current-prefix-arg query (not symbol-name)))
+ (funcall callback symbol-name))
+ (ido-mode (nrepl-ido-read-var nrepl-buffer-ns callback))
+ (t (funcall callback (read-from-minibuffer prompt symbol-name))))))
+
+(defun nrepl-doc-handler (symbol)
+ "Create a handler to lookup documentation for SYMBOL."
+ (let ((form (format "(clojure.repl/doc %s)" symbol))
+ (doc-buffer (nrepl-popup-buffer nrepl-doc-buffer t)))
+ (nrepl-send-string form
+ (nrepl-popup-eval-out-handler doc-buffer)
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session))))
+
+(defun nrepl-doc (query)
+ "Open a window with the docstring for the given QUERY.
+Defaults to the symbol at point. With prefix arg or no symbol
+under point, prompts for a var."
+ (interactive "P")
+ (nrepl-read-symbol-name "Symbol: " 'nrepl-doc-handler query))
+
+(defun nrepl-src-handler (symbol)
+ "Create a handler to lookup source for SYMBOL."
+ (let ((form (format "(clojure.repl/source %s)" symbol))
+ (src-buffer (nrepl-popup-buffer nrepl-src-buffer t)))
+ (with-current-buffer src-buffer
+ (clojure-mode)
+ (nrepl-popup-buffer-mode +1))
+ (nrepl-send-string form
+ (nrepl-popup-eval-out-handler src-buffer)
+ nrepl-buffer-ns
+ (nrepl-current-tooling-session))))
+
+(defun nrepl-src (query)
+ "Open a window with the source for the given QUERY.
+Defaults to the symbol at point. With prefix arg or no symbol
+under point, prompts for a var."
+ (interactive "P")
+ (nrepl-read-symbol-name "Symbol: " 'nrepl-src-handler query))
+
+;; TODO: implement reloading ns
+(defun nrepl-eval-load-file (form)
+ "Load FORM."
+ (let ((buffer (current-buffer)))
+ (nrepl-send-string form (nrepl-interactive-eval-handler buffer))))
+
+(defun nrepl-file-string (file)
+ "Read the contents of a FILE and return as a string."
+ (with-current-buffer (find-file-noselect file)
+ (buffer-string)))
+
+(defun nrepl-load-file-op (filename)
+ "Send \"load-file\" op for FILENAME."
+ (nrepl-send-load-file (nrepl-file-string filename)
+ filename
+ (file-name-nondirectory filename)))
+
+(defun nrepl-load-file-core (filename)
+ "Load the Clojure file FILENAME."
+ (let ((fn (replace-regexp-in-string
+ "\\\\" "\\\\\\\\"
+ (convert-standard-filename (expand-file-name filename)))))
+ (nrepl-eval-load-file
+ (format "(clojure.core/load-file \"%s\")\n(in-ns '%s)\n"
+ fn (nrepl-find-ns)))))
+
+(defun nrepl-dispatch-load-file (filename)
+ "Dispatch the load file operation for FILENAME."
+ (if (nrepl-op-supported-p "load-file")
+ (nrepl-load-file-op filename)
+ (nrepl-load-file-core filename)))
+
+(defun nrepl-load-file (filename)
+ "Load the Clojure file FILENAME."
+ (interactive (list
+ (read-file-name "Load file: " nil nil
+ nil (if (buffer-file-name)
+ (file-name-nondirectory
+ (buffer-file-name))))))
+ (remove-overlays (point-min) (point-max) 'nrepl-note-p t)
+ (nrepl-dispatch-load-file filename)
+ (message "Loading %s..." filename))
+
+(defun nrepl-load-current-buffer ()
+ "Load current buffer's file."
+ (interactive)
+ (check-parens)
+ (unless buffer-file-name
+ (error "Buffer %s is not associated with a file" (buffer-name)))
+ (when (and (buffer-modified-p)
+ (y-or-n-p (format "Save file %s? " (buffer-file-name))))
+ (save-buffer))
+ (nrepl-load-file (buffer-file-name)))
+
+(defun nrepl-recently-visited-buffer (mode)
+ "Return the most recently visited buffer whose `major-mode' is MODE.
+Only considers buffers that are not already visible."
+ (loop for buffer in (buffer-list)
+ when (and (with-current-buffer buffer (eq major-mode mode))
+ (not (string-match "^ " (buffer-name buffer)))
+ (null (get-buffer-window buffer 'visible)))
+ return buffer
+ finally (error "Can't find unshown buffer in %S" mode)))
+
+(provide 'nrepl-interaction)
+;;; nrepl-interaction.el ends here
diff --git a/nrepl-repl.el b/nrepl-repl.el
index 460fc59a..f389eb3e 100644
--- a/nrepl-repl.el
+++ b/nrepl-repl.el
@@ -31,6 +31,7 @@
;;; Code:
(require 'nrepl-client)
+(require 'nrepl-interaction)
(require 'nrepl-version)
(defface nrepl-prompt-face
diff --git a/nrepl.el b/nrepl.el
index 6724d263..bb177377 100644
--- a/nrepl.el
+++ b/nrepl.el
@@ -58,6 +58,8 @@
(require 'nrepl-client)
(require 'nrepl-version)
+(require 'nrepl-interaction)
+(require 'nrepl-eldoc)
(require 'nrepl-repl)
(require 'nrepl-repl-mode)
(require 'nrepl-selector)