2

Is this possible with stock emacs:

I'm editing a .txt file that has a snippet of bash code When I do C-x b RET I can switch to a preopened M-x shell buffer named guesuser@productionServerShellSession I have the snippet of bash code selected in a .txt file. It's a multi line pipe command ending with \ on each line. I often find myself copying the region, switching buffer and pasting and pressing enter to run the snippet in shell.

Can this be made easier with fewer key strokes?

What I did: Researched https://stackoverflow.com/questions/6286579/emacs-shell-mode-how-to-send-region-to-shell

1
  • You can write a command that does all of that. Someone else here will no doubt give you code for that.

  • Or you can use a keyboard macro: just use <f3> and <f4> to record what you are already doing and <f4> to replay it.

    See the Emacs manual, node Keyboard Macros.

  • You can also simplify the part about recopying the region by using the secondary selection. It stays the same regardless of where the cursor is, until you change it on purpose. So you can paste/yank it any number of times, regardless of where you are.

    Press and hold M- (Alt key) while you use the mouse to select the text as the secondary selection.

    If you use library second-sel.el then you can use command yank-secondary (or secondary-dwim, for more flexibility) to yank (paste) the secondary selection. I bind it to C-M-y.

    See the Emacs manual, node Secondary Selection.

  • Another way to simplify the code-copying part is to put that code in a file and insert the file each time you need the code. C-x i (command insert-file) does that.

    See the Emacs manual, node Misc File Ops.

1

Emacs provides the function process-send-region to do this. If your buffer always has the same name, you can hard-code it in:

(defun my-send (beg end)
  (interactive "r")
  (process-send-region "guesuser@productionServerShellSession" beg end))

The first line defines a new function, my-send, with two arguments, beg and end. The second line declares that this function is interactive, and will use the current region (that's what the "r" flag does). So when we call it, beg and end are filled with the start and end points of the active region.

The last line sends that region to your shell.

If you want to send the region to different buffers, you'll need to add some sort of logic to accommodate this. As a simple example, you could store your target buffer in a variable:

(defun my-set-target ()
   (interactive)
   (setq my-target (buffer-name)))

Calling this command will store the current buffer name in the variable my-target. Modifying the original command:

(defun my-send (beg end)
  (interactive "r")
  (process-send-region my-target beg end))

There are lots of ways you could smooth these out, to handle various errors (what if my-target hasn't been defined yet, or what if the region doesn't include a terminating newline, etc). But that should get you started.

Actually, I've been wanting this for a while, so I made a more complete version:

(defun tws-region-to-process (arg beg end)
  "Send the current region to a process buffer.
The first time it's called, will prompt for the buffer to
send to. Subsequent calls send to the same buffer, unless a
prefix argument is used (C-u), or the buffer no longer has an
active process."
  (interactive "P\nr")
  (if (or arg ;; user asks for selection
          (not (boundp 'tws-process-target)) ;; target not set
          ;; or target is not set to an active process:
          (not (process-live-p (get-buffer-process
                                tws-process-target)))) 
      (setq tws-process-target
            (completing-read
             "Process: "
             (seq-map (lambda (el) (buffer-name (process-buffer el)))
                      (process-list)))))
  (process-send-region tws-process-target beg end))
  • amazing how elisp can do so much in so few lines of code – american-ninja-warrior Jan 4 '18 at 17:22
  • I wish the it would let me specify the buffer name in the "completing-read", I have four shell buffers with ssh tunnels running in them, they are named using M-x shell , rename-buffer ... root@remoteserverShell , guest@remoteserverShell. These buffers are presented to me as shell. shell <1> shell <2>. and it would be better if the names were distinguishable – american-ninja-warrior Jan 4 '18 at 17:27
  • btw this is a really cool answer overall – american-ninja-warrior Jan 4 '18 at 17:30
  • agreed - try the edited version! – Tyler Jan 4 '18 at 18:19
  • wondering about alternatives to "process-send-region" because the sent text is not echoed in the shell, so I would blindly have to press enter to realize some text was sent to the prompt – american-ninja-warrior Jan 4 '18 at 20:03
1

This is a modification to Tyler's solution. It addresses two issues with the original:

  1. When a process has no buffer, which I encountered for emacs-server and ispell processes, function process-buffer returns nil, which causes function buffer-name to return the name of the current buffer, which will never be what you want, and which will clutter your completing-read selection with as many copies of the current buffer as you have processes that lack buffers.

  2. When no process buffers exist, a message to that effect should appear.

Both Tyler's original solution and this modification work not only for shell buffers, but also for term and ansi-term buffers, with a small difference. For shell buffers, the region sent will not appear in the target buffer, but for term and ansi-term buffers, that text will appear.

(defun tws-region-to-process (arg beg end) "Send the current region to a process buffer. The first time it's called, will prompt for the buffer to send to. Subsequent calls send to the same buffer, unless a prefix argument is used (C-u), or the buffer no longer has an active process." (interactive "P\nr") (when (or arg ;; user asks for selection (not (boundp 'tws-process-target)) ;; target not set ;; or target is not set to an active process: (not (process-live-p (get-buffer-process tws-process-target)))) (let (procs buf) (setq procs (remove nil (seq-map (lambda (el) (when (setq buf (process-buffer el)) (buffer-name buf))) (process-list)))) (if (not procs) (error "No process buffers currently open.") (setq tws-process-target (completing-read "Process: " procs))))) (process-send-region tws-process-target beg end))

What remains missing from this answer, and from Tyler's original, is how to send a region to an e-shell buffer. As Tyler mentioned in the comments, eshell uses a different approach that would require a different solution.

  • interesting answer. can you see the command in the target shell buffer after it's sent? (is it echoed out). that was what bugged me in Tylers solution – american-ninja-warrior Jan 10 '18 at 13:32
  • The original question referred to shell-mode, not eshell, so I'm not sure that dealing with eshell is relevant. In any case, eshell is implemented completely differently, and doesn't use an external process, so if you want to send text to it you'll have to use another route. process- or comint- functions won't do anything. – Tyler Jan 10 '18 at 19:06
  • @joshsverns: The posted solutions work not only for shell, but also for term and ansi-term. When using the solutions to send a region to a term or ansi-term process, the region text does appear. I don't know why that isn't the case when sending to a shell process. – user1404316 Jan 11 '18 at 16:24
  • @Tyler: Thanks, for creating the solution function, and for the correction. – user1404316 Jan 11 '18 at 16:28

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.