Skip to content

Commit 6824d07

Browse files
author
Mike Bruce
committed
[#209] Include inf-clojure-socket-repl
inf-clojure-socket is a helper function to create and connect to a socket REPL from within Emacs. [#209] pass :repl-type to inf-clojure-connect [#209] Automatic REPL selection by type When multiple REPLs are running, it is hardly useful to evaluate Clojurescript inside a Clojure REPL so there are two global inf-clojure buffers now for each 'sub'-language cljs & clj. When running inf-clojure functions, we take into account the major-mode before deciding which REPL should perform evaluation. [#209] Correct changelog PR link [#209] fix modeline + better socket form support [#209] close socket buffer when REPL buffer closed The user gets asked if they want to close each buffer sequentially. Or whatever their process-kill-query-function dictates. [#209] ensure space for clojure cli args [#209] remove cljs/dual buffer & keep this PR simple [#209] clean up! [#209] implement PR feedback More simplification, more consistent naming conventions and also cleaning up bugs. [#209] linting fixes [#209] bug fix with manually set socket-repl cmd [#209] include node-babashka and fix PR remarks
1 parent e555702 commit 6824d07

File tree

3 files changed

+161
-26
lines changed

3 files changed

+161
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* [#202](https://github.com/clojure-emacs/inf-clojure/issues/202): Add ClojureCLR support.
55
* [#204](https://github.com/clojure-emacs/inf-clojure/issues/204): Scroll repl buffer on insert commands
66
* [#208](https://github.com/clojure-emacs/inf-clojure/pull/208) Display message after setting repl.
7+
* [#210](https://github.com/clojure-emacs/inf-clojure/pull/210) Include `inf-clojure-socket-repl` to create a socket REPL and connect to it from inside Emacs.
78

89

910
## 3.2.1 (2022-07-22)

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ common startup forms. You can select one of these or type in your own
117117
custom startup. This will start a REPL process for the current project
118118
and you can start interacting with it.
119119

120+
If you want to use a socket REPL server, use `M-x inf-clojure-socket-repl`
121+
which will start a socket server and connect to it for you.
122+
120123
If you've already started a socket REPL server, use `M-x inf-clojure-connect`
121124
and enter its host and port numbers.
122125

inf-clojure.el

Lines changed: 157 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
;; URL: http://github.com/clojure-emacs/inf-clojure
99
;; Keywords: processes, comint, clojure
1010
;; Version: 3.2.1
11-
;; Package-Requires: ((emacs "25.1") (clojure-mode "5.11"))
11+
;; Package-Requires: ((emacs "26.2") (clojure-mode "5.11"))
1212

1313
;; This file is not part of GNU Emacs.
1414

@@ -74,10 +74,11 @@
7474
(defvar inf-clojure-startup-forms '((lein . "lein repl")
7575
(boot . "boot repl")
7676
(clojure . "clojure")
77-
(cljs . "clojure -m cljs.main -r")
77+
(cljs . "clojure -M -m cljs.main -r")
7878
(lein-clr . "lein clr repl")
7979
(planck . "planck -d")
8080
(babashka . "bb")
81+
(node-babashka . "nbb")
8182
(lumo . "lumo -d")
8283
(joker . "joker")))
8384

@@ -140,6 +141,17 @@
140141
(set-ns . "(clojure.core/in-ns '%s)")
141142
(macroexpand . "(clojure.core/macroexpand '%s)")
142143
(macroexpand-1 . "(clojure.core/macroexpand-1 '%s)")))
144+
(node-babashka . ((load . "(clojure.core/load-file \"%s\")")
145+
(doc . "(clojure.repl/doc %s)")
146+
(source . "(clojure.repl/source %s)")
147+
(arglists .
148+
"(try (-> '%s clojure.core/resolve clojure.core/meta :arglists)
149+
(catch Throwable e nil))")
150+
(apropos . "(doseq [var (sort (clojure.repl/apropos \"%s\"))] (println (str var)))")
151+
(ns-vars . "(clojure.repl/dir %s)")
152+
(set-ns . "(clojure.core/in-ns '%s)")
153+
(macroexpand . "(clojure.core/macroexpand '%s)")
154+
(macroexpand-1 . "(clojure.core/macroexpand-1 '%s)")))
143155
(clojure . ((load . "(clojure.core/load-file \"%s\")")
144156
(doc . "(clojure.repl/doc %s)")
145157
(source . "(clojure.repl/source %s)")
@@ -641,33 +653,34 @@ Customization: Entry to this mode runs the hooks on `comint-mode-hook' and
641653
642654
You can send text to the inferior Clojure process from other buffers containing
643655
Clojure source.
644-
`inf-clojure-switch-to-repl' switches the current buffer to the Clojure process buffer.
656+
`inf-clojure-switch-to-repl' switches the current buffer to the Clojure
657+
process buffer.
645658
`inf-clojure-eval-defun' sends the current defun to the Clojure process.
646659
`inf-clojure-eval-region' sends the current region to the Clojure process.
647660
648661
Prefixing the inf-clojure-eval/defun/region commands with
649-
a \\[universal-argument] causes a switch to the Clojure process buffer after sending
650-
the text.
662+
a \\[universal-argument] causes a switch to the Clojure process buffer after
663+
sending the text.
651664
652665
Commands:\\<inf-clojure-mode-map>
653-
\\[comint-send-input] after the end of the process' output sends the text from the
654-
end of process to point.
655-
\\[comint-send-input] before the end of the process' output copies the sexp ending at point
656-
to the end of the process' output, and sends it.
657-
\\[comint-copy-old-input] copies the sexp ending at point to the end of the process' output,
658-
allowing you to edit it before sending it.
659-
If `comint-use-prompt-regexp' is nil (the default), \\[comint-insert-input] on old input
660-
copies the entire old input to the end of the process' output, allowing
661-
you to edit it before sending it. When not used on old input, or if
662-
`comint-use-prompt-regexp' is non-nil, \\[comint-insert-input] behaves according to
663-
its global binding.
666+
\\[comint-send-input] after the end of the process' output sends the text from
667+
the end of process to point.
668+
\\[comint-send-input] before the end of the process' output copies the sexp
669+
ending at point to the end of the process' output, and sends it.
670+
\\[comint-copy-old-input] copies the sexp ending at point to the end of the
671+
process' output,allowing you to edit it before sending it.
672+
If `comint-use-prompt-regexp' is nil (the default), \\[comint-insert-input] on
673+
old input copies the entire old input to the end of the process' output,
674+
allowing you to edit it before sending it. When not used on old input, or if
675+
`comint-use-prompt-regexp' is non-nil, \\[comint-insert-input] behaves
676+
according to its global binding.
664677
\\[backward-delete-char-untabify] converts tabs to spaces as it moves back.
665678
\\[clojure-indent-line] indents for Clojure; with argument, shifts rest
666679
of expression rigidly with the current line.
667-
\\[indent-sexp] does \\[clojure-indent-line] on each line starting within following expression.
668-
Paragraphs are separated only by blank lines. Semicolons start comments.
669-
If you accidentally suspend your process, use \\[comint-continue-subjob]
670-
to continue it."
680+
\\[indent-sexp] does \\[clojure-indent-line] on each line starting within
681+
following expression. Paragraphs are separated only by blank lines.
682+
Semicolons start comments. If you accidentally suspend your process,
683+
use \\[comint-continue-subjob] to continue it."
671684
(setq comint-input-sender 'inf-clojure--send-string)
672685
(setq comint-prompt-regexp inf-clojure-comint-prompt-regexp)
673686
(setq mode-line-process '(":%s"))
@@ -807,9 +820,11 @@ process buffer for a list of commands.)"
807820
nil
808821
'confirm-after-completion))))
809822
(let* ((project-dir (clojure-project-dir))
810-
(process-buffer-name (if project-dir
811-
(format "inf-clojure %s" (inf-clojure--project-name project-dir))
812-
"inf-clojure"))
823+
(process-buffer-name (or
824+
inf-clojure-custom-repl-name
825+
(if project-dir
826+
(format "inf-clojure %s" (inf-clojure--project-name project-dir))
827+
"inf-clojure")))
813828
;; comint adds the asterisks to both sides
814829
(repl-buffer-name (format "*%s*" process-buffer-name)))
815830
;; Create a new comint buffer if needed
@@ -819,10 +834,11 @@ process buffer for a list of commands.)"
819834
(cmdlist (if (consp cmd)
820835
(list cmd)
821836
(split-string-and-unquote cmd)))
822-
(repl-type (or (unless prefix-arg
837+
(repl-type (or inf-clojure-socket-repl-type
838+
(unless prefix-arg
823839
inf-clojure-custom-repl-type)
824-
(car (rassoc cmd inf-clojure-startup-forms))
825-
(inf-clojure--prompt-repl-type))))
840+
(car (rassoc cmd inf-clojure-startup-forms))
841+
(inf-clojure--prompt-repl-type))))
826842
(message "Starting Clojure REPL via `%s'..." cmd)
827843
(with-current-buffer (apply #'make-comint
828844
process-buffer-name (car cmdlist) nil (cdr cmdlist))
@@ -843,6 +859,121 @@ HOST is the host the process is running on, PORT is where it's listening."
843859
(interactive "shost: \nnport: ")
844860
(inf-clojure (cons host port)))
845861

862+
(defvar-local inf-clojure-socket-callback nil
863+
"Used to transfer state between the socket process buffer & REPL buffer.")
864+
865+
(defvar-local inf-clojure-socket-buffer nil
866+
"Used to kill the associated socket buffer when it's REPL buffer is killed.")
867+
868+
(defvar inf-clojure-socket-repl-startup-message "Socket REPL listening"
869+
"Used to detect when nbb socket REPL has started since it does not print `=>'.")
870+
871+
(defun inf-clojure-socket-filter (process output)
872+
"A filter that gets triggered each time the socket receives new OUTPUT.
873+
This function prints out the output received but also
874+
watches for a prompt using the `inf-clojure-prompt' regexp, once
875+
this happens a callback is triggered if available. The callback
876+
is intended to be used to trigger a `inf-clojure-connect' once we
877+
can determine that a socket REPL is ready to receive a
878+
connection.
879+
880+
PROCESS is the process object that is being filtered.
881+
882+
OUTPUT is the latest data received from the process"
883+
(let ((server-buffer (process-buffer process)))
884+
(when (buffer-live-p server-buffer)
885+
(with-current-buffer server-buffer
886+
(insert output)))
887+
(let ((prompt-displayed (string-match inf-clojure-prompt output))
888+
(startup-displayed (string-match inf-clojure-socket-repl-startup-message output)))
889+
(when (or prompt-displayed startup-displayed)
890+
(message (format "Socket REPL startup detected for %s" (process-name process)))
891+
(with-current-buffer server-buffer
892+
(when inf-clojure-socket-callback
893+
(funcall inf-clojure-socket-callback)))))))
894+
895+
(defun inf-clojure-socket-repl-sentinel (process event)
896+
"Ensures socket REPL are cleaned up when the REPL buffer is closed.
897+
898+
PROCESS is the process object that is connected to a socket REPL.
899+
900+
EVENT is the event that triggered this function to be called."
901+
(when (not (process-live-p process))
902+
(let ((repl-buffer (process-buffer process)))
903+
(with-current-buffer repl-buffer
904+
(when inf-clojure-socket-buffer
905+
(kill-buffer inf-clojure-socket-buffer))))))
906+
907+
(defvar inf-clojure-socket-repl-startup-forms
908+
'((lein . "JVM_OPTS='-Dclojure.server.repl={:port %d :accept clojure.core.server/repl}' lein repl")
909+
(boot . "export BOOT_JVM_OPTIONS='-Dclojure.server.repl=\"{:port %d :accept clojure.core.server/repl}\"' boot repl")
910+
(clojure . "clojure -J-Dclojure.server.repl=\"{:port %d :accept clojure.core.server/repl}\"")
911+
(cljs . "clojure -J-Dclojure.server.repl=\"{:port %d :accept cljs.server.browser/repl}\"")
912+
(lein-clr . "JVM_OPTS='-Dclojure.server.repl={:port %d :accept clojure.core.server/repl}' lein clr repl")
913+
(planck . "planck -n %d")
914+
(babashka . "bb socket-repl %d")
915+
(node-babashka . "nbb socket-repl :port %d")))
916+
917+
(defcustom inf-clojure-socket-repl-port
918+
nil
919+
"Port to be used when creating a socket REPL via `inf-clojure-socket-repl'.
920+
If left as nil a random port will be selected between 5500-6000."
921+
:type '(choice integer (const nil))
922+
:package-version '(inf-clojure . "3.2.1"))
923+
924+
;;;###autoload
925+
(defun inf-clojure-socket-repl (cmd)
926+
"Start a socket REPL server and connect to it via `inf-clojure'.
927+
CMD is the command line used to start the socket REPL, if this
928+
isn't provided you will be prompted to select from the defaults
929+
provided in `inf-clojure-socket-repl-startup-forms' or
930+
`inf-clojure-custom-startup' if this is defined."
931+
(interactive (list (or (unless current-prefix-arg
932+
inf-clojure-custom-startup)
933+
(completing-read "Select Clojure socket REPL startup command: "
934+
(mapcar #'cdr inf-clojure-socket-repl-startup-forms)
935+
nil
936+
'confirm-after-completion))))
937+
(let* ((host "localhost")
938+
(port (or inf-clojure-socket-repl-port (+ 5500 (random 500))))
939+
(project-dir (clojure-project-dir))
940+
(repl-type (or (unless prefix-arg
941+
inf-clojure-custom-repl-type)
942+
(car (rassoc cmd inf-clojure-socket-repl-startup-forms))
943+
(inf-clojure--prompt-repl-type)))
944+
(project-name (inf-clojure--project-name (or project-dir "standalone")))
945+
(socket-process-name (format "*%s-%s-socket-server*" project-name repl-type))
946+
(socket-buffer-name (format "*%s-%s-socket*" project-name repl-type))
947+
(socket-buffer (get-buffer-create socket-buffer-name))
948+
(repl-buffer-name (format "%s-%s-repl" project-name repl-type))
949+
(socket-form (or (cdr (assoc repl-type inf-clojure-socket-repl-startup-forms))
950+
inf-clojure-custom-startup
951+
cmd))
952+
(socket-cmd (format socket-form port))
953+
(sock (let ((default-directory (or project-dir default-directory)))
954+
(start-file-process-shell-command
955+
socket-process-name socket-buffer
956+
socket-cmd))))
957+
(with-current-buffer socket-buffer
958+
(setq-local
959+
inf-clojure-socket-callback
960+
(lambda ()
961+
(setq inf-clojure-socket-repl-type
962+
repl-type
963+
inf-clojure-custom-repl-name
964+
repl-buffer-name
965+
repl-buffer
966+
(get-buffer-create repl-buffer-name))
967+
(inf-clojure-connect host port)
968+
(with-current-buffer (concat "*" repl-buffer-name "*")
969+
(setq inf-clojure-socket-buffer socket-buffer))
970+
(set-process-sentinel
971+
(get-buffer-process (get-buffer (concat "*" repl-buffer-name "*")))
972+
#'inf-clojure-socket-repl-sentinel))))
973+
(set-process-filter sock #'inf-clojure-socket-filter)
974+
(message "Starting %s socket REPL server at %s:%d with %s" repl-type host port socket-cmd)))
975+
976+
846977
(defun inf-clojure--forms-without-newlines (str)
847978
"Remove newlines between toplevel forms.
848979
STR is a string of contents to be evaluated. When sending

0 commit comments

Comments
 (0)