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 liketailf
.- 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.
- add
(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
allowed."
(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))
"ssh")))
(program (if in-ssh-tramp
"ssh"
(car interp)))
(args (if in-ssh-tramp
(let ((dir-name (tramp-dissect-file-name default-directory)))
(eshell-flatten-list
(list
"-t"
(tramp-file-name-host dir-name)
(format
"export TERM=xterm-256color; cd %s; exec %s"
(tramp-file-name-localname dir-name)
(string-join
(append
(list (tramp-file-name-localname (tramp-dissect-file-name (car interp))))
(cdr args))
" ")))))
(eshell-flatten-list
(eshell-stringify-list (append (cdr interp)
(cdr args))))))
(term-buf
(generate-new-buffer
(concat "*"
(if in-ssh-tramp
(format "%s %s" default-directory (string-join original-args " "))
(file-name-nondirectory program))
"*")))
(eshell-buf (current-buffer)))
(save-current-buffer
(switch-to-buffer term-buf)
(term-mode)
(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")))
(term-char-mode)
(if eshell-escape-control-x
(term-set-escape-char ?\C-x))))
nil)
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"
vc-ignore-dir-regexp
tramp-file-name-regexp))
;; 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.)