riscy / Shx For Emacs
Programming Languages
Labels
Projects that are alternatives of or similar to Shx For Emacs
#+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 [email protected]: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-promptandcomint-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=: #+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-leadervariable.) 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 ~/.emacs
:e /ssh:remote-host.com:~/.emacs
:e /docker:02fbc948e009:~/.bashrc
: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
:ssh [email protected]
:docker 8a8335d63ff3
:ssh #+end_src [[https://github.com/p3r7][Jordan Besly]] points out that you can customize the default interpreter for each "remote" [[https://www.gnu.org/software/emacs/manual/html_node/tramp/Remote-processes.html][using
connection-profile-set-local-variables]].I also use the =:kept= and =:keep= commands frequently: #+begin_src bash
wget https://bootstrap.pypa.io/get-pip.py && python get-pip.py
:keep
: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 = |
| =:eval (elisp-sexp)= | Evaluate some elisp code. Example: =:eval (pwd)= |
| =:find = | Run a fuzzy-find for |
| =:goto-url = | Completing-read for a URL |
| =:header New header= | Change the current header-line-format |
| =:kept regexp= | Show a list of your 'kept' commands matching regexp |
| =:keep= | Add the previous command to the list of kept commands |
| =:man topic= | Invoke the Emacs man page browser on a topic |
| =:ssh = | Restart the shell on the specified host |
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 = | Run a shell command after a specific delay | | =:pulse = | Repeat a shell command forever with a given delay | | =:repeat = | Repeat a shell command == times | | =:stop = | Cancel a repeating or delayed command |
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 <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,
#+begin_src elisp
(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:
- [[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