Simple shell pop-up in Emacs

Simple shell pop-up in Emacs

·

3 min read

TLDR: To practice my Emacs-Lisp-fu and revise my terminal workflow, I implemented a Guake-like pop-up terminal inside Emacs.

Emacs Rocks!

I’m trying to integrate my shell activity with my Emacs workflow. For a long while, I was using Guake-like terminals as my main scratchpad while operating my machine. After switching to i3wm, however, I gave up on the idea of being able to open a terminal in all displays for two main reasons:

  1. I don’t like how i3 scratchpad functionality integrates into the window manager process. The main reason, however, as I found the configuration DSL too limiting in making this scratchpad behave the way I like it to.
  2. Workspace switching and auto-tiling functionality are fast enough for me to reach out to my terminal.

Inside that terminal, of course, I use Tmux to take care of putting as many processes as I need into the same window.

It’s pretty straightforward to run shells in Emacs. It could integrate into my existing workflow out of the box. However, I’d like to experiment with a new setup that combines my Guake-like workflow with a single instance terminal. So, what if I can have a shell popping up in my Emacs instance whenever I need it?

As always, there are currently at least two implementations of such ideas available (you can find more suggestions here on Reddit):

  1. shell-pop-el: which, after the first time I open it, messes up magit‘s repository discovery.
  2. [eshell-toggle](https://bytemeta.vip/repo/ponimas/eshell-toggle): I couldn’t make it occupy the whole bottom real-state when there are splits in my frame.

So, instead of debugging, I thought I could try to implement it myself and exercise my elisp coding a bit. My implementation is not expected to be as generic as the packages I listed; My only goal here is to satisfy my needs and tailor them to my liking. So let’s begin. The first piece of code I need is to create the pop-up window:

(select-window (split-window
                 (frame-root-window)  ;; the frame (current frame)
                  '45                 ;; height
                  'below)))           ;; position

Quite straightforward. I’m just hard-coding it now; currently, my focus is to make it work. The next step is to check if there is an existing shell buffer. If yes, I want to bring it up (I don’t want to have redundant shells, you remember? this is my scratchpad, after all). If it doesn’t exist, however, it needs to create a new shell buffer. So the logic would be:

(let ((buffer (get-buffer "shell-buffer"))
  (if buffer
    ;; window is the return value of the previous statement
    (set-window-buffer window name)
   (progn
     ;; if shell buffer doesn't exists create it
     (eshell window)
     (rename-buffer name)))))

Now I can turn this whole thing into an easy-to-use function like:

(defun lt/eshell-pop-show (name)
  "Create a pop up window with eshell named NAME."
  (let\* ((window (split-window
                  (frame-root-window)
                  '45
                  'below))
         (buffer (get-buffer name)))
    ;; make sure we are on the current window (pop-up)
    (select-window window)
    (if buffer
        (set-window-buffer window name)
      (progn
        (eshell window)
        (rename-buffer name)))
    ))

Now I need to make another function to revert the actions of the lt/eshell-pop-show:

(defun lt/eshell-pop-hide (name)
  "Hide the existing pop up window with eshell named NAME."
  (let ((shell-buffer (get-buffer-window name)))
    (select-window shell-buffer)
    (bury-buffer)
    (delete-window)))

I get the window index of the shell-buffer (name) I have, and after switching to it, I bury the buffer and delete the window. I could also kill the buffer, but I thought it’d be nicer to always have that scratchpad available.

Now the final step is to put the toggle logic in place. If there is no visible window showing my shell, create it. Otherwise, hide the existing terminal:

(defun lt/eshell-pop-toggle ()
  "Toggle eshell pop up window."
  (interactive)
  (let ((name "shell-buffer"))
    (if (get-buffer-window name)
        (lt/eshell-pop-hide name)
      (lt/eshell-pop-show name))
    ))

Easy peasy! You can check out my complete configuration over here.

PS: Yes, I’m using eshell as well. It promises a lot; I would like to see how useful it’d be to me.