Arte EbrahimiBlogGitHub

Named Eshell Buffers

03 August, 2019 - 4 min read

Emacs is great because you can extend it and create a custom editing environment specifically tailored to your needs. I have been using emacs as my main editor for about 3 years now, but I just recently started exploring emacs lisp and utilizing it to add functionality to my editor that I would want. I feel like this is a great step forward towards unlocking the full potential of this editor.

My Goal

I noticed that I spend a lot of time switching between emacs and my terminal. I already knew that emacs has a powerful shell, eshell, that has amazing capabilities and even has the ability to execute elisp. One of my goals is to start utilizing eshell more in order to improve my workflow, and reduce the context switching from having to leave emacs.

One of my biggest issues with the default behavior of eshell in emacs, is that it is inconvenient to create new eshell buffers without manual intervention. Just to have multiple eshell buffers, you need to use M-x eshell, then call M-x rename-buffer, and repeat. Another method is to pass an integer argument to the eshell function. For example, C-u 3 M-x eshell. Neither path is quite great, and I thought there should be a better way.

I realized that this is the perfect time to create an elisp function that improves this experience for me, and integrates with my preferred completion library, ivy.

The Code

Here's the finished code. Let's go through it step by step to see what's going on.

(defun eshell-with-name ()
  (interactive)
  (let* ((eshell-buffers (seq-filter (lambda (buf)
									   (string-match-p "*eshell*" (buffer-name buf)))
									 (buffer-list)))
		 (eshell-buffer-names (mapcar (lambda (buf)
										(buffer-name buf))
									  eshell-buffers)))
    (ivy-read "eshell buffers: "
			   eshell-buffer-names
			   :action (lambda (buffer-name)
						 (let* ((eshell-buffer-exists (member buffer-name
															  (mapcar (lambda (buf)
																		(buffer-name buf))
																	  (buffer-list)))))
						   (if eshell-buffer-exists
							   (switch-to-buffer buffer-name)
							 (progn
							   (eshell 99)
							   (rename-buffer (concat "*eshell*<" buffer-name ">")))))))))

List All eshell Buffers

First, we want to list all the current eshell buffers, if we have any. We can do this by running a filter function across all of the current buffers we currently have open. We use the filter function to check and see if the string "eshell" is in the buffer name.

(seq-filter (lambda (buf)
	          (string-match-p "*eshell*" (buffer-name buf)))
	        (buffer-list))

Pass the Buffers to Ivy

Emacs buffers are actually buffer objects. In order to to pass these buffers to ivy, we need to convert them to a list of strings.

(mapcar (lambda (buf)
	      (buffer-name buf))
	    eshell-buffers)

Define an Action for ivy

Next, we need to define an action function for ivy, so that the completion library knows how to handle user input. Here's the rundown of the behavior I expected:

  • If there is no existing eshell buffer that matched the name of my input, then create a new one.
  • If there is an eshell buffer that matched the name of my input, then switch to that buffer.

To do this, I created a function that checked the inputted buffer name against all existing buffers, and if it didn't match any, I create a new eshell buffer, and rename the new buffer according to the string inputted to ivy. I passed an argument to the eshell buffer (99), hoping to prevent us from taking over an existing eshell buffer. Since I don't typically call C-u 99 M-x eshell, this will work out (for me at least).

(lambda (buffer-name)
  (let* ((eshell-buffer-exists (member buffer-name
				                       (mapcar (lambda (buf)
						                         (buffer-name buf))
					                           (buffer-list)))))
    (if eshell-buffer-exists
		(switch-to-buffer buffer-name)
	  (progn
	    (eshell 99)
	    (rename-buffer (concat "*eshell*<" buffer-name ">"))))))

Overall Functionality

And there you have it. We now have a function that can spawn multiple named eshell buffers, and utilizes our completion library to switch to an eshell buffer if it already exists. Since I am now easily able to create new eshell buffers on the fly, I am hoping I will be more inclined to use eshell compared to constantly switching to my terminal. All in all, I'm excited to explore all that eshell has to offer, and I look forward to writing a new post about my improved workflows through using it! Thanks for reading!