Skip to content

Instantly share code, notes, and snippets.

Last active Jan 19, 2022
What would you like to do?
my eshell config


eshell is the shell I’ve tried using over time, and in the end just never stick with it. Let’s try one more time with a couple of tricks. The first tricks are mostly documentation:

  • M-& in a tramp-aware session means you run commands in a new buffer, without a TTY. Great for things like tailf.
  • for ncurses-like applications, “visual commands” is the missing context. Applications like top are in the default list by default, but let’s:
    • add htop to that default list, because I occasionally use it
    • and remember about eshell-exec-visual, which runs the command as a visual command.
(require 'em-term)

(add-to-list 'eshell-visual-commands "htop")

That said, I also need to automatically close the command buffer on exit, because who wants stale buffers.

(setq eshell-destroy-buffer-when-process-dies t)

One last thing, that turns out to be very annoying: eshell-exec-visual is not tramp-aware. Let’s make it tramp-aware for SSH at least:

 (defun eshell-exec-visual (&rest args)
   "Run the specified PROGRAM in a terminal emulation buffer.
 ARGS are passed to the program.  At the moment, no piping of input is
   (let* (eshell-interpreter-alist
	   (original-args args)
	   (interp (eshell-find-interpreter (car args) (cdr args)))
	   (in-ssh-tramp (and (tramp-tramp-file-p default-directory)
			      (equal (tramp-file-name-method
				      (tramp-dissect-file-name default-directory))
	   (program (if in-ssh-tramp
		      (car interp)))
	   (args (if in-ssh-tramp
		     (let ((dir-name (tramp-dissect-file-name default-directory)))
			 (tramp-file-name-host dir-name)
			  "export TERM=xterm-256color; cd %s; exec %s"
			  (tramp-file-name-localname dir-name)
			    (list (tramp-file-name-localname (tramp-dissect-file-name (car interp))))
			    (cdr args))
			   " ")))))
		    (eshell-stringify-list (append (cdr interp)
						   (cdr args))))))
	     (concat "*"
		     (if in-ssh-tramp
			 (format "%s %s" default-directory (string-join original-args " "))
			 (file-name-nondirectory program))
	   (eshell-buf (current-buffer)))
	(switch-to-buffer term-buf)
	(set (make-local-variable 'term-term-name) eshell-term-name)
	(make-local-variable 'eshell-parent-buffer)
	(setq eshell-parent-buffer eshell-buf)
	(term-exec term-buf program program nil args)
	(let ((proc (get-buffer-process term-buf)))
	  (if (and proc (eq 'run (process-status proc)))
	      (set-process-sentinel proc 'eshell-term-sentinel)
	    (error "Failed to invoke visual command")))
	(if eshell-escape-control-x
	    (term-set-escape-char ?\C-x))))

Besides that, let’s have some sane default settings.

For example, default history size is 128 items. lol.

(setq eshell-history-size 1000000)

(Side note: this is a nice trick to import all of bash history. Only run once though: eshell-read-history ~/.bash_history.)

Let’s also make tramp a bit faster:

 ;; cache file-name forever
 (setq remote-file-name-inhibit-cache nil)

 ;; make sure vc stuff is not making tramp slower
 (setq vc-ignore-dir-regexp
	(format "%s\\|%s"

 ;; not sure why we have this? just cargo-culting from an answer I saw
 ;; online.
 (setq tramp-verbose 1)

 ;; projectile has the fun side-effect of wanting to calculate the
 ;; project name, which makes tramp oh-so-much-slower.
 (setq projectile-mode-line "Projectile")

But the real performance gain is making sure that the ~/.ssh/config file has this:

Host *
    ControlPath ~/.ssh/master-%h:%p
    ControlMaster auto
    ControlPersist 10m

(aka: make every SSH connection use the “control master” openssh feature, which opens a single SSH connection, reuses it when you create new channels, and keeps it around for 10 minutes until after you’ve closed your last channel.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment