-\\[comint-send-input] after the end of the process' output sends the text from the
- end of process to point.
-\\[comint-send-input] before the end of the process' output copies the sexp ending at point
- to the end of the process' output, and sends it.
-\\[comint-copy-old-input] copies the sexp ending at point to the end of the process' output,
- allowing you to edit it before sending it.
-If `comint-use-prompt-regexp' is nil (the default), \\[comint-insert-input] on old input
- copies the entire old input to the end of the process' output, allowing
- you to edit it before sending it. When not used on old input, or if
- `comint-use-prompt-regexp' is non-nil, \\[comint-insert-input] behaves according to
- its global binding.
+\\[comint-send-input] after the end of the process' output sends the text from
+ the end of process to point.
+\\[comint-send-input] before the end of the process' output copies the sexp
+ ending at point to the end of the process' output, and sends it.
+\\[comint-copy-old-input] copies the sexp ending at point to the end of the
+ process' output,allowing you to edit it before sending it.
+If `comint-use-prompt-regexp' is nil (the default), \\[comint-insert-input] on
+ old input copies the entire old input to the end of the process' output,
+ allowing you to edit it before sending it. When not used on old input, or if
+ `comint-use-prompt-regexp' is non-nil, \\[comint-insert-input] behaves
+ according to its global binding.
\\[backward-delete-char-untabify] converts tabs to spaces as it moves back.
\\[clojure-indent-line] indents for Clojure; with argument, shifts rest
of expression rigidly with the current line.
-\\[indent-sexp] does \\[clojure-indent-line] on each line starting within following expression.
-Paragraphs are separated only by blank lines. Semicolons start comments.
-If you accidentally suspend your process, use \\[comint-continue-subjob]
-to continue it."
+\\[indent-sexp] does \\[clojure-indent-line] on each line starting within
+ following expression. Paragraphs are separated only by blank lines.
+ Semicolons start comments. If you accidentally suspend your process,
+ use \\[comint-continue-subjob] to continue it."
(setq comint-input-sender 'inf-clojure--send-string)
(setq comint-prompt-regexp inf-clojure-comint-prompt-regexp)
(setq mode-line-process '(":%s"))
+ ;; NOTE: Using Tree-sitter based syntax highlighting in comint
+ ;; buffer is currently not possible.
(clojure-mode-variables)
(clojure-font-lock-setup)
(when inf-clojure-enable-eldoc
@@ -755,21 +800,39 @@ to suppress the usage of the target buffer discovery logic."
The name is simply the final segment of the path."
(file-name-nondirectory (directory-file-name dir)))
+(defun inf-clojure--project-dir ()
+ "Return current Clojure project root."
+ (let ((project-vc-extra-root-markers '("project.clj" ; Leiningen
+ "build.gradle" ; Gradle
+ "build.gradle.kts" ; Gradle
+ "deps.edn" ; Clojure CLI (a.k.a. tools.deps)
+ "shadow-cljs.edn" ; shadow-cljs
+ "bb.edn" ; babashka
+ "nbb.edn" ; nbb
+ "basilisp.edn" ; Basilisp (Python)
+ )))
+ (project-root (project-current))))
+
+(defvar clojure-ts-mode-syntax-table)
+
;;;###autoload
-(defun inf-clojure (cmd)
- "Run an inferior Clojure process, input and output via buffer `*inf-clojure*'.
-If there is a process already running in `*inf-clojure*', just
+(defun inf-clojure (cmd &optional suppress-message)
+ "Run an inferior Clojure process, input and output via buffer *inf-clojure*.
+If there is a process already running in *inf-clojure*, just
switch to that buffer.
CMD is a string which serves as the startup command or a cons of
host and port.
- Prompts user for repl startup command and repl type if not
+ Prompts user for REPL startup command and REPL type if not
inferrable from startup command. Uses `inf-clojure-custom-repl-type'
and `inf-clojure-custom-startup' if those are set.
Use a prefix to prevent using these when they
are set.
+Prints a message that it has connected to the host and port
+unless SUPPRESS-MESSAGE is truthy.
+
Runs the hooks from `inf-clojure-mode-hook' (after the
`comint-mode-hook' is run). \(Type \\[describe-mode] in the
process buffer for a list of commands.)"
@@ -779,10 +842,12 @@ process buffer for a list of commands.)"
(mapcar #'cdr inf-clojure-startup-forms)
nil
'confirm-after-completion))))
- (let* ((project-dir (clojure-project-dir))
- (process-buffer-name (if project-dir
- (format "inf-clojure %s" (inf-clojure--project-name project-dir))
- "inf-clojure"))
+ (let* ((project-dir (inf-clojure--project-dir))
+ (process-buffer-name (or
+ inf-clojure-custom-repl-name
+ (if project-dir
+ (format "inf-clojure %s" (inf-clojure--project-name project-dir))
+ "inf-clojure")))
;; comint adds the asterisks to both sides
(repl-buffer-name (format "*%s*" process-buffer-name)))
;; Create a new comint buffer if needed
@@ -796,25 +861,131 @@ process buffer for a list of commands.)"
inf-clojure-custom-repl-type)
(car (rassoc cmd inf-clojure-startup-forms))
(inf-clojure--prompt-repl-type))))
- (message "Starting Clojure REPL via `%s'..." cmd)
+ (unless suppress-message
+ (message "Starting Clojure REPL via `%s'..." cmd))
(with-current-buffer (apply #'make-comint
process-buffer-name (car cmdlist) nil (cdr cmdlist))
(inf-clojure-mode)
- (set-syntax-table clojure-mode-syntax-table)
+ (set-syntax-table (pcase (car (inf-clojure--get-preferred-major-modes))
+ ('clojure-ts-mode clojure-ts-mode-syntax-table)
+ (_ clojure-mode-syntax-table)))
(setq-local inf-clojure-repl-type repl-type)
(hack-dir-local-variables-non-file-buffer))))
;; update the default comint buffer and switch to it
(setq inf-clojure-buffer (get-buffer repl-buffer-name))
(if inf-clojure-repl-use-same-window
(pop-to-buffer-same-window repl-buffer-name)
- (pop-to-buffer repl-buffer-name))))
+ (pop-to-buffer repl-buffer-name))
+ repl-buffer-name))
-;;;###autoload
-(defun inf-clojure-connect (host port)
+;;;###autol
+(defun inf-clojure-connect (host port &optional suppress-message)
"Connect to a running socket REPL server via `inf-clojure'.
-HOST is the host the process is running on, PORT is where it's listening."
+HOST is the host the process is running on, PORT is where it's
+listening. SUPPRESS-MESSAGE is optional and if truthy will
+prevent showing the startup message."
(interactive "shost: \nnport: ")
- (inf-clojure (cons host port)))
+ (inf-clojure (cons host port) suppress-message))
+
+(defvar-local inf-clojure-socket-callback nil
+ "Used to transfer state between the socket process buffer & REPL buffer.")
+
+(defvar-local inf-clojure-socket-buffer nil
+ "Used to kill the associated socket buffer when it's REPL buffer is killed.")
+
+(defun inf-clojure-socket-filter (process output)
+ "A filter that gets triggered each time the socket receives new OUTPUT.
+This function prints out the output received but also
+watches for a prompt using the `inf-clojure-prompt' regexp, once
+this happens a callback is triggered if available. The callback
+is intended to be used to trigger a `inf-clojure-connect' once we
+can determine that a socket REPL is ready to receive a
+connection.
+
+PROCESS is the process object that is being filtered.
+
+OUTPUT is the latest data received from the process"
+ (let ((server-buffer (process-buffer process)))
+ (when (buffer-live-p server-buffer)
+ (with-current-buffer server-buffer
+ (insert output)))
+ (let ((prompt-displayed (string-match inf-clojure-prompt output)))
+ (when prompt-displayed
+ (with-current-buffer server-buffer
+ (when inf-clojure-socket-callback
+ (funcall inf-clojure-socket-callback)))))))
+
+(defun inf-clojure-socket-repl-sentinel (process _event)
+ "Ensures socket REPL are cleaned up when the REPL buffer is closed.
+
+PROCESS is the process object that is connected to a socket REPL.
+
+EVENT is the event that triggered this function to be called."
+ (when (not (process-live-p process))
+ (let ((repl-buffer (process-buffer process)))
+ (with-current-buffer repl-buffer
+ (when inf-clojure-socket-buffer
+ (kill-buffer inf-clojure-socket-buffer))))))
+
+(defvar inf-clojure-socket-repl-startup-forms
+ '((lein . "JVM_OPTS='-Dclojure.server.repl={:port %d :accept clojure.core.server/repl}' lein repl")
+ (boot . "export BOOT_JVM_OPTIONS='-Dclojure.server.repl=\"{:port %d :accept clojure.core.server/repl}\"' boot repl")
+ (clojure . "clojure -J-Dclojure.server.repl=\"{:port %d :accept clojure.core.server/repl}\"")
+ (cljs . "clojure -J-Dclojure.server.repl=\"{:port %d :accept cljs.server.browser/repl}\"")
+ (lein-clr . "JVM_OPTS='-Dclojure.server.repl={:port %d :accept clojure.core.server/repl}' lein clr repl")
+ (planck . "planck -n %d")
+ (babashka . "bb socket-repl %d")))
+
+(defcustom inf-clojure-socket-repl-port
+ nil
+ "Port to be used when creating a socket REPL via `inf-clojure-socket-repl'.
+If left as nil a random port will be selected between 5500-6000."
+ :type '(choice integer (const nil))
+ :package-version '(inf-clojure . "3.3"))
+
+;;;###autoload
+(defun inf-clojure-socket-repl (cmd)
+ "Start a socket REPL server and connects to it via `inf-clojure-connect'.
+CMD is the command line instruction used to start the socket
+REPL. It should be a string with \"%d\" in it to take a random
+port. Set `inf-clojure-custom-startup' or choose from the
+defaults provided in `inf-clojure-socket-repl-startup-forms'."
+ (interactive (list (or (unless current-prefix-arg
+ inf-clojure-custom-startup)
+ (completing-read "Select Clojure socket REPL startup command: "
+ (mapcar #'cdr inf-clojure-socket-repl-startup-forms)
+ nil
+ 'confirm-after-completion))))
+ (let* ((host "localhost")
+ (port (or inf-clojure-socket-repl-port (+ 5500 (random 500))))
+ (project-dir (inf-clojure--project-dir))
+ (repl-type (or (unless prefix-arg
+ inf-clojure-custom-repl-type)
+ (car (rassoc cmd inf-clojure-socket-repl-startup-forms))
+ (inf-clojure--prompt-repl-type)))
+ (project-name (inf-clojure--project-name (or project-dir "standalone")))
+ (socket-process-name (format "*%s-%s-socket-server*" project-name repl-type))
+ (socket-buffer (get-buffer-create
+ (format "*%s-%s-socket*" project-name repl-type)))
+ (socket-cmd (format cmd port))
+ (sock (let ((default-directory (or project-dir default-directory)))
+ (start-file-process-shell-command
+ socket-process-name socket-buffer
+ socket-cmd))))
+ (with-current-buffer socket-buffer
+ (setq-local
+ inf-clojure-socket-callback
+ (lambda ()
+ (let* ((inf-clojure-custom-repl-type repl-type)
+ (created-repl-buffer (inf-clojure-connect host port :suppress-message)))
+ (with-current-buffer (get-buffer created-repl-buffer)
+ (setq-local inf-clojure-socket-buffer socket-buffer)
+ (set-process-sentinel
+ (get-buffer-process (current-buffer))
+ #'inf-clojure-socket-repl-sentinel))))))
+ (set-process-filter sock #'inf-clojure-socket-filter)
+ (message "Starting %s socket REPL server at %s:%d with %s" repl-type host port socket-cmd)))
+
(defun inf-clojure--forms-without-newlines (str)
"Remove newlines between toplevel forms.
@@ -825,17 +996,24 @@ of forms."
(condition-case nil
(with-temp-buffer
(progn
- (clojurec-mode)
+ ;; Activate preferred major mode.
+ (funcall (car (inf-clojure--get-preferred-major-modes)))
(insert str)
(whitespace-cleanup)
(goto-char (point-min))
(while (not (eobp))
(while (looking-at "\n")
(delete-char 1))
+ ;; NOTE: There is no special API for that in
+ ;; `clojure-ts-mode', so probably for now lets keep this
+ ;; `clojure-mode' function.
(unless (eobp)
(clojure-forward-logical-sexp))
(unless (eobp)
- (forward-char)))
+ (forward-char)
+ ;; Remove an empty line at the end of the buffer.
+ (when (eobp)
+ (delete-char -1))))
(buffer-substring-no-properties (point-min) (point-max))))
(scan-error str)))
@@ -865,7 +1043,7 @@ current defun else return the string.."
(let ((end (point))
(case-fold-search t)
(func (if bounds #'cons #'buffer-substring-no-properties)))
- (beginning-of-defun)
+ (beginning-of-defun-raw)
(funcall func (point) end))))
(defun inf-clojure-eval-defun (&optional and-go)
@@ -903,16 +1081,21 @@ Prefix argument AND-GO means switch to the Clojure buffer afterwards."
"Insert FORM into process and evaluate.
Indent FORM. FORM is expected to have been trimmed."
(let ((clojure-process (inf-clojure-proc)))
- (with-current-buffer (process-buffer clojure-process)
- (comint-goto-process-mark)
- (let ((beginning (point)))
- (insert (format "%s" form))
- (let ((end (point)))
- (goto-char beginning)
- (indent-sexp end)
- ;; font-lock the inserted code
- (font-lock-ensure beginning end)))
- (comint-send-input t t))))
+ ;; ensure the repl buffer scrolls. See similar fix in CIDER:
+ ;; https://github.com/clojure-emacs/cider/pull/2590
+ (with-selected-window (or (get-buffer-window inf-clojure-buffer)
+ (selected-window))
+ (with-current-buffer (process-buffer clojure-process)
+ (comint-goto-process-mark)
+ (let ((beginning (point)))
+ (insert form)
+ (let ((end (point)))
+ (goto-char beginning)
+ (indent-sexp end)
+ ;; font-lock the inserted code
+ (font-lock-ensure beginning end)
+ (goto-char end)))
+ (comint-send-input t t)))))
(defun inf-clojure-insert-defun ()
"Send current defun to process."
@@ -926,10 +1109,10 @@ Indent FORM. FORM is expected to have been trimmed."
(buffer-substring-no-properties (save-excursion (backward-sexp) (point))
(point))))
-;;; Now that inf-clojure-eval-/defun/region takes an optional prefix arg,
-;;; these commands are redundant. But they are kept around for the user
-;;; to bind if he wishes, for backwards functionality, and because it's
-;;; easier to type C-c e than C-u C-c C-e.
+;; Now that inf-clojure-eval-/defun/region takes an optional prefix arg,
+;; these commands are redundant. But they are kept around for the user
+;; to bind if he wishes, for backwards functionality, and because it's
+;; easier to type C-c e than C-u C-c C-e.
(defun inf-clojure-eval-region-and-go (start end)
"Send the current region to the inferior Clojure, and switch to its buffer.
@@ -942,18 +1125,11 @@ START and END are the beginning and end positions in the buffer to send."
(interactive)
(inf-clojure-eval-defun t))
-(defvar inf-clojure-prev-l/c-dir/file nil
+(defvar inf-clojure-prev-loaded-dir-and-file nil
"Record last directory and file used in loading or compiling.
This holds a cons cell of the form `(DIRECTORY . FILE)'
describing the last `inf-clojure-load-file' command.")
-(defcustom inf-clojure-source-modes '(clojure-mode)
- "Used to determine if a buffer contains Clojure source code.
-If it's loaded into a buffer that is in one of these major modes, it's
-considered a Clojure source file by `inf-clojure-load-file'.
-Used by this command to determine defaults."
- :type '(repeat symbol))
-
(defun inf-clojure-load-file (&optional switch-to-repl file-name)
"Load a Clojure file into the inferior Clojure process.
@@ -963,34 +1139,45 @@ is present it will be used instead of the current file."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(file-name (or file-name
- (car (comint-get-source "Load Clojure file: " inf-clojure-prev-l/c-dir/file
+ (car (comint-get-source "Load Clojure file: " inf-clojure-prev-loaded-dir-and-file
;; nil because doesn't need an exact name
- inf-clojure-source-modes nil))))
+ (inf-clojure--get-preferred-major-modes) nil))))
(load-form (inf-clojure-get-feature proc 'load)))
(comint-check-source file-name) ; Check to see if buffer needs saved.
- (setq inf-clojure-prev-l/c-dir/file (cons (file-name-directory file-name)
+ (setq inf-clojure-prev-loaded-dir-and-file (cons (file-name-directory file-name)
(file-name-nondirectory file-name)))
(inf-clojure--send-string proc (format load-form file-name))
(when switch-to-repl
(inf-clojure-switch-to-repl t))))
+(declare-function clojure-ts-find-ns "clojure-ts-mode")
+
+(defun inf-clojure--find-ns ()
+ "Return the namespace of the current Clojure buffer.
+
+This function delegates its job to an appropritate function, considering
+`inf-clojure-source-modes'."
+ (pcase (car (inf-clojure--get-preferred-major-modes))
+ ('clojure-ts-mode (clojure-ts-find-ns))
+ (_ (clojure-find-ns))))
+
(defun inf-clojure-reload (arg)
"Send a query to the inferior Clojure for reloading the namespace.
-See variable `inf-clojure-reload-form' and
+See variable `inf-clojure-reload-form' and variable
`inf-clojure-reload-all-form'.
The prefix argument ARG can change the behavior of the command:
- - C-u M-x `inf-clojure-reload': prompts for a namespace name.
- - M-- M-x `inf-clojure-reload': executes (require ... :reload-all).
- - M-- C-u M-x `inf-clojure-reload': reloads all AND prompts."
+ - \\`C-u' \\[inf-clojure-reload]: prompts for a namespace name.
+ - \\`M--' \\[inf-clojure-reload]: executes (require ... :reload-all).
+ - \\`M--' \\`C-u' \\[inf-clojure-reload]: reloads all AND prompts."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(reload-all-p (or (equal arg '-) (equal arg '(-4))))
(prompt-p (or (equal arg '(4)) (equal arg '(-4))))
(ns (if prompt-p
- (car (inf-clojure-symprompt "Namespace" (clojure-find-ns)))
- (clojure-find-ns)))
+ (car (inf-clojure-symprompt "Namespace" (inf-clojure--find-ns)))
+ (inf-clojure--find-ns)))
(form (if (not reload-all-p)
(inf-clojure-reload-form proc)
(inf-clojure-reload-all-form proc))))
@@ -1017,7 +1204,7 @@ display."
(if (zerop (length ans)) default ans))))
-;;; Adapted from function-called-at-point in help.el.
+;; Adapted from function-called-at-point in help.el.
(defun inf-clojure-fn-called-at-pt ()
"Return the name of the function called in the current call.
The value is nil if it can't find one."
@@ -1091,7 +1278,7 @@ STRING if present."
(prin1-to-string (substring-no-properties string))))
nil
(expand-file-name inf-clojure--log-file-name
- (clojure-project-dir))
+ (inf-clojure--project-dir))
'append
'no-annoying-write-file-in-minibuffer)))
@@ -1199,11 +1386,11 @@ for evaluation, therefore FORM should not include it."
(defun inf-clojure-arglists (fn)
"Send a query to the inferior Clojure for the arglists for function FN.
See variable `inf-clojure-arglists-form'."
- (when-let ((proc (inf-clojure-proc 'no-error)))
- (when-let ((arglists-form (inf-clojure-get-feature proc 'arglists)))
- (thread-first (format arglists-form fn)
- (inf-clojure--process-response proc "(" ")")
- (inf-clojure--some)))))
+ (when-let* ((proc (inf-clojure-proc 'no-error))
+ (arglists-form (inf-clojure-get-feature proc 'arglists)))
+ (thread-first (format arglists-form fn)
+ (inf-clojure--process-response proc "(" ")")
+ (inf-clojure--some))))
(defun inf-clojure-show-arglists (prompt-for-symbol)
"Show the arglists for function FN in the mini-buffer.
@@ -1225,8 +1412,8 @@ prefix argument PROMPT-FOR-NS, it prompts for a namespace name."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(ns (if prompt-for-ns
- (car (inf-clojure-symprompt "Ns vars" (clojure-find-ns)))
- (clojure-find-ns)))
+ (car (inf-clojure-symprompt "Ns vars" (inf-clojure--find-ns)))
+ (inf-clojure--find-ns)))
(ns-vars-form (inf-clojure-get-feature proc 'ns-vars)))
(inf-clojure--send-string proc (format ns-vars-form ns))))
@@ -1238,8 +1425,8 @@ PROMPT-FOR-NS, it prompts for a namespace name."
(interactive "P")
(let* ((proc (inf-clojure-proc))
(ns (if prompt-for-ns
- (car (inf-clojure-symprompt "Set ns to" (clojure-find-ns)))
- (clojure-find-ns)))
+ (car (inf-clojure-symprompt "Set ns to" (inf-clojure--find-ns)))
+ (inf-clojure--find-ns)))
(set-ns-form (inf-clojure-get-feature proc 'set-ns)))
(when (or (not ns) (equal ns ""))
(user-error "No namespace selected"))
@@ -1320,22 +1507,13 @@ evaluating \\[inf-clojure-completion-form] at the REPL."
(funcall inf-clojure-completions-fn
(inf-clojure--process-response completion-expr proc "(" ")"))))))
-(defconst inf-clojure-clojure-expr-break-chars "^[] \"'`><,;|&{()[@\\^]"
- "Regexp are hard.
+(defconst inf-clojure-clojure-expr-break-chars "^][ \"'`><,;|&{()@\\^"
+ "A list of characters that serve as expression boundaries.
-This regex has been built in order to match the first of the
-listed chars. There are a couple of quirks to consider:
-
-- the ] is always a special in elisp regex so you have to put it
- directly AFTER [ if you want to match it as literal.
-- The ^ needs to be escaped with \\^.
-
-Tests and `re-builder' are your friends.")
+See `inf-clojure-completion-bounds-of-expr-at-point'.")
(defun inf-clojure--kw-to-symbol (kw)
- "Convert the keyword KW to a symbol.
-
-This guy was taken from CIDER, thanks folks."
+ "Convert the keyword KW to a symbol."
(when kw
(replace-regexp-in-string "\\`:+" "" kw)))
diff --git a/test/inf-clojure-tests.el b/test/inf-clojure-tests.el
index 6023338..af6dea9 100644
--- a/test/inf-clojure-tests.el
+++ b/test/inf-clojure-tests.el
@@ -88,9 +88,8 @@
(ict-with-assess-buffers
((a (insert "@")))
(with-current-buffer a
- (expect
- (ict-bounds-string (inf-clojure-completion-bounds-of-expr-at-point))
- :not :to-be nil))))
+ (expect (inf-clojure-completion-bounds-of-expr-at-point)
+ :to-be nil))))
(it "computes bounds for [symbol"
(ict-with-assess-buffers
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy