[[https://melpa.org/#/shx][https://melpa.org/packages/shx-badge.svg]] [[https://stable.melpa.org/#/shx][https://stable.melpa.org/packages/shx-badge.svg]] [[https://github.com/riscy/shx-for-emacs/actions][https://github.com/riscy/shx-for-emacs/workflows/test/badge.svg]]
[[file:img/screenshot.png]]
Table of Contents :TOC_3_gh:noexport:
[[#description][Description]]
[[#install][Install]]
[[#setup][Setup]]
[[#key-bindings][Key bindings]]
[[#markup-in-the-shell][Markup in the shell]]
[[#extra-shell-commands][Extra shell commands]]
[[#related][Related]]
Description /shx/ or "shell-extras" extends comint-mode in Emacs (e.g. =M-x shell=).
It's compatible with any underlying REPL (zsh, bash, psql, ipython, etc.).
It parses the output stream in a few useful ways:
shx makes it easy to add new shell commands written in elisp. Some are already built in:
It also extends =shell-mode='s syntax highlighting, recenters and highlights content for better viewing when you run commands like ~comint-previous-prompt~ and ~comint-kill-input~, and improves compatibility with evil-mode by anticipating when to switch to insert mode.
Use =M-x shx RET= to start a new shell session with ~shx-mode~ enabled.
/This version is tested with Emacs 26.1/. Check out the [[https://github.com/riscy/shx-for-emacs/releases][release log]].
Install From MELPA =M-x package-install RET shx RET= to install =shx= from [[https://melpa.org/][MELPA]]. From GNU Guix =guix install emacs-shx= to install =shx= from [[https://guix.gnu.org/][GNU Guix]]. *** Manually Add the following to your =.emacs=:
(add-to-list 'load-path "~/path/to/shx/") ; add shx.el's directory to the load-path (require 'shx) ; load shell-extras
Setup *** Quick-start Type =M-x shx RET=. Try out the following commands:
*** Enable automatically If you like shx-mode, you can enable it everywhere:
#+begin_src elisp
(shx-global-mode 1) ; toggle shx-mode on globally
#+end_src
Now shx will run automatically in any =comint-mode= buffer. If you don't want
shx to run in every comint-mode buffer, you can use =M-x shx-mode= on a
case-by-case basis, or just add hooks to the mode in question, for example:
#+begin_src elisp
(add-hook 'inferior-python-mode-hook #'shx-mode)
#+end_src
*** Customize Use =M-x customize-group RET shx RET= to see shx's many customization options. Here's an example customization using ~setq~:
(setq
;; resync the shell's default-directory with Emacs on "z" commands:
shx-directory-tracker-regexp "^z "
;; vastly improve display performance by breaking up long output lines
shx-max-output 1024
;; prevent input longer than macOS's typeahead buffer from going through
shx-max-input 1024
;; prefer inlined images and plots to have a height of 250 pixels
shx-img-height 250
;; don't show any incidental hint messages about how to use shx
shx-show-hints nil
;; flash the previous comint prompt for a full second when using C-c C-p
shx-flash-prompt-time 1.0
;; use `#' to prefix shx commands instead of the default `:'
shx-leader "#")
#+end_src
Key bindings | Key binding | Description | |-------------+--------------------------------------------------------------------------| | =C-RET= | If the cursor is not on the prompt, paste the current line to the input | | =RET= | If the cursor is on a filename or a URL, try to open it | | =SPC= | If the prompt is =:=, send =SPC= straight through to the process | | =q= | If the prompt is =:=, send =q= straight through to the process |
Note the prompt will be =:= when reading through the output of =less= or a =man= page if you run the following:
(setenv "LESS" "--dumb --prompt=s")
Markup in the shell shx's markup can enhance basic command-line applications and drive other events.
If the output ever contains =
<command arg1 arg2 ...>
where ~command~ is a shx command and ~arg1 ... argn~ is a space-separated list of arguments. Arguments don't need to be surrounded by quotes -- the command will figure out how to parse them.
You can use this markup to create a barplot (=:plotbar=) after collecting some stats, or generate an =:alert= when a task is finished, and so forth.
Extra shell commands shx's 'extra' commands are invoked by typing a =:= followed by the command's name. (You can change the =:= prefix by customizing the ~shx-leader~ variable.) These commands are written in elisp and so can access all of Emacs' facilities. Type =:help= to see a complete listing of shx commands.
One command I use frequently is the =:edit= (shorthand =:e=) command:
:edit ~/.emacs
:e /ssh:remote-host.com:~/.emacs
:e /docker:02fbc948e009:~/.bashrc
:sedit /etc/passwd
Thanks to [[https://github.com/CeleritasCelery][CeleritasCelery]] it's also possible to use environment variables in the argument list:
:e $HOME/.emacs.d
(To see an environment variable's value, use ~(getenv "")~.)
The =:ssh= and =:docker= commands are popular for opening "remote" shells:
:ssh user@remote-host.com
:docker 8a8335d63ff3
:ssh
[[https://github.com/p3r7][Jordan Besly]] points out that you can customize the default interpreter for each "remote" using [[https://www.gnu.org/software/emacs/manual/html_node/tramp/Remote-processes.html][connection-profile-set-local-variables]].
I also use the =:kept= and =:keep= commands frequently:
wget https://bootstrap.pypa.io/get-pip.py && python get-pip.py
:keep
:kept pip
Because these commands are written in elisp, shx gives =M-x shell= a lot of the same advantages as =eshell=. You can even evaluate elisp code directly in the buffer (see =:help eval=).
*** General commands
| Command | Description |
|----------------------+-------------------------------------------------------|
| =:alert= | Reveal the buffer with an alert. Useful for markup |
| =:clear= | Clear the buffer |
| =:date= | Show the date (even when the process is blocked) |
| =:diff file1 file2= | Launch an Emacs diff between two files |
| =:edit file= | Edit a file. Shortcut: =:e
There are more than this -- type =:help= for a listing of all user commands.
*** Graphical commands | Command | Description | |------------------------------+------------------------| | =:view image_file.jpg= | Display an image | | =:plotbar data_file.txt= | Display a bar plot | | =:plotline data_file.txt= | Display a line plot | | =:plotmatrix data_file.txt= | Display a heatmap | | =:plotscatter data_file.txt= | Display a scatter plot | | =:plot3d data_file.txt= | Display a 3D plot |
These are for displaying inline graphics and plots in the shell buffer. You
can control how much vertical space an inline image occupies by customizing
the ~shx-img-height~ variable.
Note =convert= (i.e. ImageMagick) and =gnuplot= need to be installed. If
the binaries are installed but these commands aren't working, customize the
~shx-path-to-convert~ and ~shx-path-to-gnuplot~ variables to point to the
binaries. Also note these graphical commands aren't yet compatible with
shells launched on remote hosts (e.g. over ssh or in a Docker container).
*** Asynchronous commands
| Command | Description |
|-----------------------------------+---------------------------------------------------|
| =:delay
Use these to delay, pulse, or repeat a command a specific number of times.
Unfortunately these only support your typical shell commands, and not shx's
extra (colon-prefixed) commands. So this possible:
#+begin_src bash
# Run the 'pwd' command 10 seconds from now:
:delay 10 pwd
#+end_src
But this is not possible:
#+begin_src bash
# Run the 'pwd' shx command 10 seconds from now (DOES NOT WORK)
:delay 10 :pwd
#+end_src
* Adding new commands New shx commands are written by defining single-argument elisp functions named ~shx-cmd-COMMAND-NAME~, where ~COMMAND-NAME~ is what the user would type to invoke it. *** Example: a command to rename the buffer If you evaluate the following (or add it to your ~.emacs~),
(defun shx-cmd-rename (name)
"(SAFE) Rename the current buffer to NAME."
(if (not (ignore-errors (rename-buffer name)))
(shx-insert 'error "Can't rename buffer.")
(shx-insert "Renaming buffer to " name "\n")
(shx--hint "Emacs won't save buffers starting with *")))
#+end_src
then each shx buffer will immediately have access to the =:rename= command.
When it's invoked, shx will also display a hint about buffer names.
Note the importance of defining a docstring. This documents the
command so that typing =:help rename= will give the user information on what
the command does. Further, since the docstring begins with =(SAFE)=,
it becomes part of shx's markup language. So in this case if:
#+begin_src xml
<rename A new name for the buffer>
#+end_src
appears on a line by itself in the output, the buffer will try to
automatically rename itself.
***** Example: invoking ediff from the shell A command similar to this one is built into shx:
(defun shx-cmd-diff (files)
"(SAFE) Launch an Emacs `ediff' between FILES."
(setq files (shx-tokenize files))
(if (not (eq (length files) 2))
(shx-insert 'error "diff <file1> <file2>\n")
(shx-insert "invoking ediff...\n")
(shx--asynch-funcall #'ediff (mapcar #'expand-file-name files))))
#+end_src
Note that ~files~ is supplied as a string, but it's immediately parsed
into a list of strings using ~shx-tokenize~. Helpfully, this function is
able to parse various styles of quoting and escaping, for example
~(shx-tokenize "'file one' file\\ two")~
evaluates to
~("file one" "file two")~.
***** Example: a command to browse URLs If you execute the following,
(defun shx-cmd-browse (url)
"Browse the supplied URL."
(shx-insert "Browsing " 'font-lock-keyword-face url)
(browse-url url))
#+end_src
then each shx buffer will have access to the =:browse= command.
Note the docstring does not specify that this command is =SAFE=.
This means =<browse url>= will not become part of shx's markup. That
makes sense in this case, since you wouldn't want to give a process the
power to open arbitrary URLs without prompting.
Related If you're here, these might be interesting:
And if running a =dumb= terminal in Emacs isn't for you, here are some alternatives: