shx-for-emacs
shx-for-emacs copied to clipboard
An Emacs shell-mode (and comint-mode) extension that enables displaying small plots and graphics and lets users write shell commands in Emacs Lisp.
#+TITLE: shx for Emacs #+OPTIONS: toc:3 author:t creator:nil num:nil #+AUTHOR: Chris Rayner #+EMAIL: [email protected]
[[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]]
- [[#from-melpa][From MELPA]]
- [[#from-gnu-guix][From GNU Guix]]
- [[#manually][Manually]]
- [[#setup][Setup]]
- [[#quick-start][Quick-start]]
- [[#enable-automatically][Enable automatically]]
- [[#customize][Customize]]
- [[#key-bindings][Key bindings]]
- [[#markup-in-the-shell][Markup in the shell]]
- [[#extra-shell-commands][Extra shell commands]]
- [[#general-commands][General commands]]
- [[#graphical-commands][Graphical commands]]
- [[#asynchronous-commands][Asynchronous commands]]
- [[#adding-new-commands][Adding new 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:
- Display graphics and plots in the shell with a simple markup
language (e.g. =
=) - Add event-driven and timed behaviors to any shell session
- Open any filename or URL by arrowing up to it and pressing =RET= (shx will even try to guess the correct directory)
- Yank any line to the prompt by arrowing up to it and pressing =C-RET=
- Check the time a command was run by mousing over its prompt
shx makes it easy to add new shell commands written in elisp. Some are already built in:
- =:clear= clears the buffer (like =clear= or =Command-K= on macOS)
- =:e filename.txt= opens a file for editing
- =:ssh user@host:port= starts a remote shell session using tramp
- =:view image_file.png= embeds an image in the shell
- =:plotline data_file.txt= embeds a line plot
- etc.
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]].
- Display graphics and plots in the shell with a simple markup
language (e.g. =
-
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=: #+begin_src elisp (add-to-list 'load-path "~/path/to/shx/") ; add shx.el's directory to the load-path (require 'shx) ; load shell-extras #+end_src
-
Setup *** Quick-start Type =M-x shx RET=. Try out the following commands:
- =:e ~/.bashrc= to edit your =.bashrc= (for example)
- =:man ls= to display the man page for =ls=
- =:help= to a start a completing read for other =shx= 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~:
#+begin_src elisp
(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: #+begin_src elisp (setenv "LESS" "--dumb --prompt=s") #+end_src
-
Markup in the shell shx's markup can enhance basic command-line applications and drive other events.
If the output ever contains =
= on a line by itself, then a scaled rendering of =mountains.png= will be inlined within the text in the shell. This works because =view= is a shx command. shx will execute any (safe) shx command that appears with the following syntax: #+begin_src xml <command arg1 arg2 ...> #+end_src 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: #+begin_src bash
edit the .emacs file:
:edit ~/.emacs
use tramp to edit .emacs on a remote host through ssh:
:e /ssh:remote-host.com:~/.emacs
use tramp to edit .bashrc on a running docker container:
:e /docker:02fbc948e009:~/.bashrc
edit a local file as root
:sedit /etc/passwd #+end_src
Thanks to [[https://github.com/CeleritasCelery][CeleritasCelery]] it's also possible to use environment variables in the argument list: #+begin_src bash :e $HOME/.emacs.d #+end_src (To see an environment variable's value, use ~(getenv "")~.)
The =:ssh= and =:docker= commands are popular for opening "remote" shells: #+begin_src bash
open a shell on a remote host:
:ssh [email protected]
connect to a running docker container
:docker 8a8335d63ff3
reopen the shell on the localhost:
:ssh #+end_src [[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: #+begin_src bash
write a complicated command:
wget https://bootstrap.pypa.io/get-pip.py && python get-pip.py
save the last command:
:keep
search for commands having to do with pip:
:kept pip #+end_src
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~), #+begin_src elisp (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:
#+begin_src elisp
(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
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:
- [[https://www.masteringemacs.org/article/shell-comint-secrets-history-commands][Shell & Comint Secrets: History commands]]
- [[https://www.masteringemacs.org/article/pcomplete-context-sensitive-completion-emacs][PComplete: Context-Sensitive Completion in Emacs]]
- [[https://dev.to/_darrenburns/10-tools-to-power-up-your-command-line-4id4][10 tools to power up your command line]]
- [[https://www.booleanworld.com/customizing-coloring-bash-prompt/][Creating dynamic bash prompts]]
- [[https://github.com/Orkohunter/keep][The Keep Utility]] inspired the =kept= and =keep= commands
- [[https://terminalsare.sexy/]["Terminals Are Sexy"]] (portal)
- [[https://github.com/riscy/command_line_lint][Command-Line Lint]], another project I maintain
- [[http://ohmyz.sh/][oh my zsh]], a community-driven zsh configuration
- [[https://github.com/Bash-it/bash-it][bash-it]], a community driven bash configuration
And if running a =dumb= terminal in Emacs isn't for you, here are some alternatives:
- [[https://leanpub.com/the-tao-of-tmux/read][The Tao of tmux]], re: working in the terminal with tmux
- [[https://github.com/zsh-users/zsh-syntax-highlighting][zsh-syntax-highlighting]]
- [[https://hackernoon.com/macbook-my-command-line-utilities-f8a121c3b019#.clz934ly3][Shell configuration tips]] from Vitaly Belman
- [[http://www.iterm2.com/documentation-shell-integration.html][Shell integration]] for iTerm2 on macOS
- [[https://getbitbar.com/][BitBar]] adds program output to menus on macOS