BIRKEY CONSULTING

ABOUT  RSS  ARCHIVE


Posts tagged "eshell":

01 Aug 2021

Why Eshell? - Part 5

One of the feature of Eshell that took me sometime to really appreciate is its built-in ability to emulate Plan9 smart Shell. It allows you to run a script or a command, run it again by just hitting enter key after you modify it, say you made a mistake or want to change part of it using Emacs editing power. You might say that you can do same thing in any terminal using your up arrow key and command line editing. But I challenge you to try it until you fully realize the advantage you have vs regular terminal. Below is a simple screen recording to show you what I mean:

eshell-plan9-smart-shell.gif

I recommend you read following post to learn more about smart shell and more about eshell including aliases: https://masteringemacs.org/article/complete-guide-mastering-eshell.

This concludes my `Why Eshell` series. Hope you find it useful and happy Eshelling!

Tags: eshell emacs
17 Jul 2021

Why Eshell? - part 4

Since eshell buffer is just a regular emacs buffer, we have all of the emacs power at our disposal. In part 3 of my post, I briefly alluded to multiple terminal management by just using few lines of elisp and all without using any third party packages. I am posting the main elisp function for posterity here:

(defun krun (cmd)
  (interactive
   (list
    (ido-completing-read
     "Enter cmd to run (append ##name for buffer name): "
     (let ((history nil))
       ;; We have to build up a list ourselves from the ring vector.
       (dotimes (index (ring-length keshell-history-global-ring))
	 (push (ring-ref keshell-history-global-ring index) history))
       ;; Show them most-recent-first.
       (setq history (nreverse history))))))
  (let* ((cmds (split-string cmd "##"))
	 (tag (or (-> cmds second)
		  "kshell"))
	 (buff-name (-> tag s-chomp s-trim)))
    (kshell cmd buff-name)))

The above function (along with those from the previous post) will allow you to do following:

Following demonstrates above cases I am talking about:

eshell-buffer-management.gif
Tags: eshell emacs
10 Jul 2021

Why Eshell? - Part 3

If you have been following my `Why Eshell` series, you might be wondering about interactive ido completion for eshell. Since Eshell buffer is just a regular emacs buffer, we can compose few emacs builtin functionalities to bend it to our command line work flow. Following is all the code you need. Note that it is heavily commented so you can understand what is happening.

(defun kshell-with-name (&optional name)
  ;; creates an eshell buffer with `kshell' as the default name. Take
  ;; optional name to override
  (let ((m (->> (or name "kshell")
		(format "*%s*"))))
    (if (get-buffer m)
	(pop-to-buffer m)
      (progn
	(eshell)
	(rename-buffer m)))))

(defun kshell (&optional cmd buff-name send-input)
  ;; interactive fn that you can call via M-x or hotkey. It detects
  ;; current project root to start eshell buffer if no `buff-name' is
  ;; given. You can control how the `cmd` gets insert only or insert
  ;; and executed using `send-input` flag. Ex. Useful for further
  ;; editing.
  (interactive)
  (let ((dir (projectile-project-root))) ;; you need projectile
    (if buff-name
	(kshell-with-name buff-name)
      (kshell-with-name))
    (eshell/clear-scrollback)
    (insert (format "cd %s" (or dir "~/")))
    (eshell-send-input)
    (insert (format "%s" cmd))
    (when send-input (eshell-send-input))))

(defun krun (cmd)
  ;; eshell command history with ido completion. Assign it to a hot
  ;; key say, F12 and you will get a search-able command history that
  ;; you can execute just by doing ido interactive search.
  (interactive
   (list
    (ido-completing-read
     "Enter cmd to run (append ##name for buffer name): "
     (let ((history nil))
       ;; We have to build up a list ourselves from the ring vector.
       (dotimes (index (ring-length keshell-history-global-ring))
	 (push (ring-ref keshell-history-global-ring index) history))
       ;; Show them most-recent-first.
       (setq history (nreverse history))))))
  (let* ((cmds (split-string cmd "##")) ;; you need s.el lib
	 (tag (or (-> cmds second)
		  "kshell"))
	 (buff-name (-> tag s-chomp s-trim)))
    (kshell cmd buff-name)))

Here is the screen recording in action:

eshell-ido-interactive.gif

I am using F12 to invoke `krun` and my cross session eshell history shows up in the minibuffer where I can use ido completion to select a command that I can run. As an added benefit, above code allows one to bring a eshell buffer with specific name by adding ##name at the end of the command if one wants to have the command run in a dedicated buffer.

Tags: eshell emacs
27 Jun 2021

Why Eshell? - Part 2

Among many reasons of why I use eshell as my main terminal, I listed following in part 1 of my `Why Eshell?` series blog post. I am listing it here for posterity:

  1. Long running command notification and time
  2. Cross session history
  3. Interactive ido completion
  4. Unified interface (shell prompt buffer as regular emacs buffer)
  5. Plan9 Style Shell prompt (Think of it as bash REPL)
  6. Multiple terminal management
  7. Super charge bash with elisp
  8. Eshell aliases that puts bash aliases to shame :)

I addressed first point here: notification and time. Let me address second point, which is sharing eshell history across many terminal sessions.

(defvar keshell-history-global-ring nil
  "The history ring shared across Eshell sessions.")

(defun keshell-hist-use-global-history ()
  "Make Eshell history shared across different sessions."
  (unless keshell-history-global-ring
    (when eshell-history-file-name
      (eshell-read-history nil t))
    (setq keshell-history-global-ring
	  (or eshell-history-ring (make-ring eshell-history-size))))
  (setq eshell-history-ring keshell-history-global-ring))
;; Following hook enables it
(add-hook 'eshell-mode-hook 'keshell-hist-use-global-history)
;; Following removes the hook
(remove-hook 'eshell-mode-hook 'keshell-hist-use-global-history)

After evaluating above 12 lines of elisp, you will have a list that holds all the eshell entries across sessions, which you can persist into a history file (just uses bash history file) and can even share it across machines over network. This opens up a whole new possibilities of completions, deduplication and multiple eshell buffer management goodness, which I will cover in the next few parts of `Why Eshell?` posts. So stay tuned and happy eshelling!

Tags: eshell emacs
20 Jun 2021

Why Eshell? - Part 1

From time to time, I got asked why I use eshell as my main terminal. There are multiple reasons I do so and covering all of them in one post would be too long. Instead, I would like to start with a high level bullet points and address each one in a separate blog post with working code examples where I can point my coworkers and friends to them so they can pick and choose as they wish.

  1. Long running command notification and time
  2. Cross session history
  3. Interactive ido completion
  4. Unified interface (shell prompt buffer as regular emacs buffer)
  5. Plan9 Style Shell prompt (Think of it as bash REPL)
  6. Multiple terminal management
  7. Super charge bash with elisp
  8. Eshell aliases that puts bash aliases to shame :)

Let us start with how you can accomplish the first item from the list above. Following 18 lines of elisp will give you the super power of:

;; eshell time and notification
(defvar-local eshell-current-command-start-time nil)

(defun eshell-current-command-start ()
  (setq eshell-current-command-start-time (current-time)))

(defun eshell-current-command-stop ()
  (when eshell-current-command-start-time
    (let ((elapsed-time (float-time
			 (time-subtract (current-time)
					eshell-current-command-start-time))))
      (if (> elapsed-time 30)
	  (tooltip-show (format "Finished in: %.0fs" elapsed-time))
	(eshell-interactive-print
	 (format "Time: %.0fs\n" elapsed-time))))
    (setq eshell-current-command-start-time nil)))

(defun eshell-current-command-time-track ()
  (add-hook 'eshell-pre-command-hook #'eshell-current-command-start nil t)
  (add-hook 'eshell-post-command-hook #'eshell-current-command-stop nil t))

;; This line below installs time tracking and notification
(add-hook 'eshell-mode-hook #'eshell-current-command-time-track)
;; Once you eval above snippet in emacs, fire up M-x eshell, and type:
sleep 40
;; You can switch away from emacs and will be notified that above
;; command took 40s to run
;; To uninstall
;; (remove-hook 'eshell-mode-hook #'eshell-current-command-time-track)

Try doing above with some bashrc voodoo or plug-in that you have no control over. I have been there and done that and it is one of many reasons why I use eshell now. I hope someone finds it useful to his or her command line work flow. Happy eshelling!

Tags: emacs eshell
Other posts