1

I have cider working just fine in Emacs with my Clojure code. I am following a tutorial that uses Cursive as the IDE; in the tutorial they were able to define a key binding to a line of code in a specific namespace.

The command I want to run in the cider REPL and the namespace user is

(go)

When I hit C-M-S-g, it should fire in the REPL (I have a key that hits C-M-S all at once on my keyboard).

This command is for starting my server and I will have others for halting and resetting, but it would be nice instead of going to the REPL and typing them in to have a key binding to do so.

So how do I define key bindings that would run specific commands in the REPL?

2

1 Answer 1

1
+50

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.