As I understand it, you basically want to run a command in the REPL. That is, I think you're saying you want to press C-M-S-g
and have it execute (go)
in the REPL.
Emacs interacts with processes through a comint
, or command interpreter. For example,
Shell mode is a derivative of Comint mode, a general-purpose mode for
communicating with interactive subprocesses. Most of the features of
Shell mode actually come from Comint mode...
https://www.gnu.org/software/emacs/manual/html_node/emacs/Shell-Mode.html
The cider REPL must be communicating with a process. Therefore, it must be a comint.
I know next to nothing about Clojure or CIDER. Rather than try to give an answer tailored to those, I will give something general which should address the problem, as I understand it, and be useful elsewhere. This is from my init and is one of my favorite hacks.
A common task for me is copying text from one buffer to another. Sometimes that buffer is just a regular buffer, other times it's a comint. Often, I don't know where I'll need to copy text to. The target must be determined "on demand".
The following defines a new variable, xc/on-demand-window
. It defines the target for where text should go. Note that this is defined in terms of windows1. This makes the target location fixed.
(defvar xc/on-demand-window nil
"Target on-demand window.
An on-demand window is one which you wish to return to within the
current Emacs session but whose importance doesn't warrant a
permanent binding.")
The next function makes defining the target window more user friendly. Call xc/on-demand-window
to set the target to the currently selected window. In your case, once the REPL is up, select that window and call this.
(defun xc/on-demand-window-set ()
"Set the value of the on-demand window to current window."
(interactive)
(setq xc/on-demand-window (selected-window))
(message "Set on-demand window to: %s" xc/on-demand-window))
It's handy to jump between the source window and the target. Call xc/on-demand-window-goto
to do that.
(defun xc/on-demand-window-goto ()
"Goto the `xc/on-demand-window'."
(interactive)
(let ((win xc/on-demand-window))
(unless win (error "No on-demand window set! See `xc/on-demand-window-set'."))
(if (eq (selected-window) xc/on-demand-window)
(error "Already in `xc/on-demand-window'"))
(let ((frame (window-frame win)))
(raise-frame frame)
(select-frame frame)
(select-window win))))
Ultimately, you want to send text to the REPL. The function xc/send-line-or-region
is for interactively sending text. It's a bit of overkill but like I said, it was already in my init. It will send a line or region to the on-demand window (or current window if the on-demand window is not set). If the on-demand window's buffer has no process, the line is simply inserted. However, if the buffer has a process, as should be the case with the REPL, the line will be inserted and sent to the process. It has the added option to insert a new line for non-text buffers.
(defun xc/send-line-or-region (&optional advance buff beg end)
"Send region or line to BUFF.
If buffer has a process, insert and send line to the process. If
no process, then simply insert text at point. Create a new line
when ADVANCE is non-nil. Use current region if BEG and END not
provided. If no region provided, send entire line. Default BUFF
is the buffer associated with `xc/on-demand-window'."
(interactive (if (use-region-p)
(list nil nil (region-beginning) (region-end))
(list nil nil nil nil)))
(let* ((beg (or beg (if (use-region-p) (region-beginning)) nil))
(end (or end (if (use-region-p) (region-end)) nil))
(substr (string-trim
(or (and beg end (buffer-substring-no-properties beg end))
(buffer-substring-no-properties (line-beginning-position) (line-end-position)))))
(buff (or buff (window-buffer xc/on-demand-window)))
(proc (get-buffer-process buff)))
(if substr
(with-selected-window (get-buffer-window buff t)
(let ((window-point-insertion-type t)) ; advance marker on insert
(cond (proc
(goto-char (process-mark proc))
(insert substr)
(comint-send-input nil t))
(t
(insert substr)
(if advance
(progn
(end-of-line)
(newline-and-indent)))))))
(error "Invalid selection"))))
Send a string to the on-demand window with xc/send-string
. This is probably more to your question. It works the same way as xc/send-line-or-region
, but acts on a fixed string. When called with M-x
, it will prompt for what string to send. I also added a history so that you can press up/down or M-p
/M-n
to see previously sent strings.
(defvar xc--send-string-history nil
"History of strings sent via `xc/send-string'")
(defun xc/send-string (string &optional advance buff)
"Send STRING to BUFF'.
Default BUFF is the buffer associated with
`xc/on-demand-window' (or current window if not set). If BUFF
has an associated process, send region as input, otherwise just
insert the region. Create a new line when ADVANCE is non-nil."
(interactive
(let* ((prompt (format "Send string to % s: " (window-buffer xc/on-demand-window)))
(cmd (read-string prompt "" 'xc--on-demand-send-string-history)))
(list cmd nil nil)))
(let* ((buff (or buff (window-buffer xc/on-demand-window)))
(proc (get-buffer-process buff)))
(with-selected-window (get-buffer-window buff t)
(let ((window-point-insertion-type t)) ; advance marker on insert
(cond (proc
(goto-char (process-mark proc))
(insert string)
(comint-send-input nil t))
(t
(insert string)
(if advance
(progn
(end-of-line)
(newline-and-indent)))))))))
You can then do what you want with something like:
(global-set-key (kbd "C-M-S-g") '(lambda () (interactive) (xc/send-string "(go)")))
Hopefully that helps! Happy hacking!
NOTE The xc/send-line-or-region
and xc/send-string
commands use string-trim
which doesn't seem to autoload. You may need to call (require 'subr-x)
first.
EDIT The previous code handles sending to the REPL. The link you gave on the 5th gives a clue about handling namespaces.
Since namespaces are part of the language used by the comint/REPL, they must be handled within the string sent to the comint/REPL.
I've not used Clojure, but modifying the "essential pattern", the string that needs to be sent is (or is something like):
"(require 'user) (user/go (go))"
So, using xc/send-string
,
(global-set-key (kbd "C-M-S-g") '(lambda () (interactive) (xc/send-string "(require 'user) (user/go (go))"))
Honestly, though, it looks like this function from the article you linked would do something comparable if the sexp before point were (go)
(and the appropriate some-namespace
/some-namespace-fn
replacement made):
(defun custom-eval-last-sexp ()
(interactive)
(cider-interactive-eval
(format "(require 'some-namespace)
(some-namespace/some-fn %s)"
(cider-last-sexp))))
Hopefully that's enough for you to modify things to your need.
1 Although we need to send text to a buffer, I've found that it's actually the window I want to interact with. It's somewhat arbitrary since it's possible to get the buffer from a window and vice-versa. I've done it both ways and this is what has survived in my init. Feel free, of course, to try it the other way.