I’ve been moving over to Emacs eshell from zsh, inspired in part by a reddit post by Pierre Neidhardt and re-motivated by an excellent video by Howard Abrams. One thing I missed from zsh was fish-like history autosuggestions, which “suggest” a previous shell command based on your current input.
I thought company-mode could be repurposed to provide such autosuggestions,
since its front end is pretty similar to what I wanted and you get the
completion framework for free. And with the help of pcomplete, ivy,
completion-at-point and emacs-fish-completion, I was already covered on
zsh-like completion, so company-mode was really more in the way than anything.
All that’s needed is to define some functions for the autosuggest backend1:
(defun company-eshell-autosuggest-candidates (prefix) (let* ((history (delete-dups (mapcar (lambda (str) (string-trim (substring-no-properties str))) (ring-elements eshell-history-ring)))) (most-similar (cl-find-if (lambda (str) (string-prefix-p prefix str)) history))) (when most-similar `(,most-similar)))) (defun company-eshell-autosuggest--prefix () (let ((prefix (string-trim-left (buffer-substring-no-properties (save-excursion (eshell-bol)) (save-excursion (end-of-line) (point)))))) (if (not (string-empty-p prefix)) prefix 'stop))) (defun company-eshell-autosuggest (command &optional arg &rest ignored) (interactive (list 'interactive)) (cl-case command (interactive (company-begin-backend 'company-eshell)) (prefix (and (eq major-mode 'eshell-mode) (company-eshell-autosuggest--prefix))) (candidates (company-eshell-autosuggest-candidates arg))))
All we’re doing here is defining a company backend that grabs the first history entry that has the current eshell input as a prefix, and providing it as a completion candidate via company. To enable this, we just need to enable company-mode in eshell and set company-backends locally in eshell-mode 2:
(add-hook 'eshell-mode-hook #'company-mode) ; Not needed if using global-company-mode (defun setup-eshell-autosuggest () (with-eval-after-load 'company (setq-local company-backends '(company-eshell-autosuggest)) (setq-local company-frontends '(company-preview-frontend))))
company-preview-frontend just gets rid of the
company popup, and uses the dark overlay by default. This is fine since at any
given point there is only one completion candidate. Of course it would be
simple to tweak this and provide all history entries that match the current
input as well as use the popup, but I wanted to be consistent with the
fish-like behavior for now.
Here’s a screencap of it in action:
You can just hit
RET to accept the autosuggest, or you can bind a key of your