Commit f0007576 authored by Ambrevar's avatar Ambrevar

emacs-eshell-versus-shell: Init.

parent 782900a2
Pipeline #160541707 passed with stages
in 1 minute and 7 seconds
#+TITLE: Eshell versus M-x shell
I've [[../emacs-eshell/index.org][used and defended Eshell]] for years. Sadly, Eshell has some long
standing issues that I grew tired of in the long run. So I've decided to switch
to =M-x shell= and see how much of my Eshell workflow I could port.
* Language and the underlying shell program
The benefit of using Bash is that it's the /de facto/ standard for sharing shell
commands on the Internet. As such, =M-x shell= can run any of those shell
snippets out there without extra modification.
Eshell constantly requires modifications to the syntax, for instance turning
=$(...)= to =${...}= or the input redirection =<= to a pipe.
You might complain that Bash is a terrible language and Eshell's Elisp is vastly
superior. But even at the language level, =M-x shell= beats Eshell since it
lets the user choose which underlying program to run (with =C-u M-x shell=). So
the same mode can run =psql=, =bash=, or [[https://github.com/willghatch/racket-rash][=rash=]] if you fancy a Racket-powered
shell. And Racket is a much more interesting language than Eshell in my
opinion :)
* Virtualenv and =guix environment=
=virtualenv= and other environment managers don't work well with Eshell.
Maybe it would be possible to leverage the =direnv= Emacs package, but I haven't
looked into it yet.
I've heard that =nix-env= has support for Eshell, so I suppose it would be
equally possible to add Eshell support to =guix environment=.
Until then, =M-x shell= has better support for environment managers than Eshell.
* Completion
Eshell only offers poor completion by default.
=M-x shell= can do slightly better if the underlying shell is Bash, but the
latter does not always provide great completion.
It's possible to greatly enhance completion with support from the Fish shell.
The =fish-completion= package supports both =M-x shell= and Eshell.
You can even display the Fish inline documentation with the
=helm-fish-completion= package.
* Performance
=M-x shell= is significantly faster than Eshell at processing long outputs.
To the point that performance is rarely an issue (never was for me at least).
In Eshell, it was not uncommon to see Emacs grind to a halt because of too long
an output.
=M-x shell= is still an order of magnitude slower than, say, =emacs-vterm=. The
performance issue is significantly reduced when =font-lock-mode= is off. There
may be a few other tricks to boost =M-x shell= performance.
* Command duration
In Eshell, I implemented a special prompt that reports the duration of the previous
command. This is very useful because I don't have to think /beforehand/ whether
I should prefix my command with =time=. And I typically don't want to run a
slow command twice just to know how much time it took.
This Eshell prompt is available in the =eshell-prompt-extras= package:
#+begin_src elisp
(setq eshell-prompt-function #'epe-theme-multiline-with-status)
#+end_src
Sadly, there is no equivalent to =eshell-post-command-hook= for =M-x shell=
which makes it impossible to implement using the same (sane) logic.
So I had to resort to a hack. I added the following to my =.bashrc=
#+begin_src sh
PS1='\e[1;37m[\e[1;32m\w\e[1;37m]\e[m \e[0;34m\D{%F %T}\e[m\n\e[1;37m\$\e[m '
#+end_src
to get a prompt looking like this:
#+begin_quote
[~/dotfiles] 2020-06-26 16:36:30
$ echo Hi!
#+end_quote
With a bit of Elisp, I was able to write a [[https://gitlab.com/ambrevar/dotfiles/-/blob/adce533dc244f3e4fd5c146172eb48981c73b5b2/.emacs.d/lisp/init-shell.el#L68][=ambrevar/shell-command-duration=]]
command that parses the prompt and reports the time a command took.
It's far from perfect but it's a start.
* Helm-system-packages
As mentioned above, =M-x shell= does not have an equivalent to
=eshell-post-command-hook= which makes it impossible to use it for
=helm-system-package=.
* Extra syntax highlighting (fontification)
Comint-mode, the major mode behind =M-x shell=, supports extra fontification by
default. For instance, running
#+begin_src sh
$ ip addr
#+end_src
prints the network interfaces in a different colour. Neat, isn't it?
* Helm "Switch to shell"
One of the features I use the most is the "Switch to to Eshell" action from
=helm-find-files=. Until recently, it only supported Eshell.
This is now fixed in [[https://github.com/emacs-helm/helm/commit/6061a3af5139491d7811c86d7c9cd5fec8f5fb40][Helm 3.6.3]] and I just had to add this to my initialization
file:
#+begin_src elisp
(setq helm-ff-preferred-shell-mode 'shell-mode)
#+end_src
I can now quickly fuzzy-search and switch to the directly I want without ever
typing a single =cd= command.
* Narrow-to-prompt
A very convenient Emacs command is =narrow-to-defun= (=C-x n d=): it focuses the
buffer on a single function definition and all buffer-global commands are
restricted to it. For instance, if I want to replace all occurrences of =foo=
in a given function, without altering the other =foo= in the rest of the buffer,
I can first narrow to the function, then run =query-replace= over all visible
occurrences.
I wanted to do the same with shells. Indeed, it's very useful to be able to
restrict commands to the output of a given command, say, to search an output
without hitting matches from other outputs in the same buffer.
I wrote an implementation both for [[https://gitlab.com/ambrevar/dotfiles/-/blob/adce533dc244f3e4fd5c146172eb48981c73b5b2/.emacs.d/lisp/init-eshell.el#L312][Eshell]] and [[https://gitlab.com/ambrevar/dotfiles/-/blob/adce533dc244f3e4fd5c146172eb48981c73b5b2/.emacs.d/lisp/init-shell.el#L84][=M-x shell=]].
* Browsing prompts
It's useful to search prompts, maybe to copy a command or to consult its output
again.
Helm can fuzzy-search and browse the prompts of all shell (including Eshell)
buffers with =helm-comint-prompts-all= and =helm-eshell-prompts-all=.
* Emacs integration
One of the benefits of Eshell is that it integrates shell commands with Emacs.
For instance, running =grep= will display an interactive result in an Emacs
buffer.
It's possible to write an =emacsclient= wrapper that evaluates the command
passed as argument in an Emacs buffer to, so it's possible to mimic this feature
of Eshell rather closely.
Still, this is not as closely integrated to Emacs as it could get. Indeed,
Eshell can intertwine its shell language with Elisp. It's thus able to run any
Elisp function.
Maybe a good direction to explore is [[http://howardism.org/Technical/Emacs/piper-presentation-transcript.html][piper]].
* Conclusion
I'm happy with =M-x shell=, the everyday use is much smoother than that of
Eshell. Performance being one of the biggest selling point in my experience.
Overall, with the support of few packages such as Helm and =helm-fish-completion=
I get a stellar shell experience. I miss very few features, such as support for
"visual" commands, modifiers and predicates which I rarely use.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment