{"id":13646478,"url":"https://github.com/riscy/shx-for-emacs","last_synced_at":"2025-04-07T15:07:41.705Z","repository":{"id":94012583,"uuid":"4196491","full_name":"riscy/shx-for-emacs","owner":"riscy","description":"An Emacs shell-mode (and comint-mode) extension that enables displaying small plots and graphics and lets users write shell commands in Emacs Lisp.","archived":false,"fork":false,"pushed_at":"2024-05-12T15:30:51.000Z","size":2099,"stargazers_count":227,"open_issues_count":15,"forks_count":11,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-03-31T12:07:24.648Z","etag":null,"topics":["comint-mode","emacs","shell"],"latest_commit_sha":null,"homepage":"","language":"Emacs Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/riscy.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-05-01T21:00:43.000Z","updated_at":"2025-03-08T03:23:29.000Z","dependencies_parsed_at":"2024-08-02T01:38:32.633Z","dependency_job_id":null,"html_url":"https://github.com/riscy/shx-for-emacs","commit_stats":null,"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riscy%2Fshx-for-emacs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riscy%2Fshx-for-emacs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riscy%2Fshx-for-emacs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/riscy%2Fshx-for-emacs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/riscy","download_url":"https://codeload.github.com/riscy/shx-for-emacs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247675597,"owners_count":20977376,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["comint-mode","emacs","shell"],"created_at":"2024-08-02T01:02:56.973Z","updated_at":"2025-04-07T15:07:41.668Z","avatar_url":"https://github.com/riscy.png","language":"Emacs Lisp","funding_links":[],"categories":["Emacs Lisp"],"sub_categories":[],"readme":"#+TITLE: shx for Emacs\n#+OPTIONS: toc:3 author:t creator:nil num:nil\n#+AUTHOR: Chris Rayner\n#+EMAIL: dchrisrayner@gmail.com\n\n[[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]]\n\n[[file:img/screenshot.png]]\n\n* Table of Contents :TOC_3_gh:noexport:\n- [[#description][Description]]\n- [[#install][Install]]\n    - [[#from-melpa][From MELPA]]\n    - [[#from-gnu-guix][From GNU Guix]]\n    - [[#manually][Manually]]\n- [[#setup][Setup]]\n    - [[#quick-start][Quick-start]]\n    - [[#enable-automatically][Enable automatically]]\n    - [[#customize][Customize]]\n- [[#key-bindings][Key bindings]]\n- [[#markup-in-the-shell][Markup in the shell]]\n- [[#extra-shell-commands][Extra shell commands]]\n    - [[#general-commands][General commands]]\n    - [[#graphical-commands][Graphical commands]]\n    - [[#asynchronous-commands][Asynchronous commands]]\n    - [[#adding-new-commands][Adding new commands]]\n- [[#related][Related]]\n\n* Description\n  /shx/ or \"shell-extras\" extends comint-mode in Emacs (e.g. =M-x shell=).\n\n  It's compatible with any underlying REPL (zsh, bash, psql, ipython, etc.).\n\n  It parses the output stream in a few useful ways:\n  - *Display graphics and plots* in the shell with a simple markup\n    language (e.g. =\u003cview image.png\u003e=)\n  - *Add event-driven and timed behaviors* to any shell session\n  - *Open any filename or URL* by arrowing up to it and pressing =RET= (shx will\n    even try to guess the correct directory)\n  - *Yank any line to the prompt* by arrowing up to it and pressing =C-RET=\n  - *Check the time a command was run* by mousing over its prompt\n\n  shx makes it easy to add new shell commands written in elisp.  Some are\n  already built in:\n  - =:clear= clears the buffer (like =clear= or =Command-K= on macOS)\n  - =:e filename.txt= opens a file for editing\n  - =:ssh user@host:port= starts a remote shell session using tramp\n  - =:view image_file.png= embeds an image in the shell\n  - =:plotline data_file.txt= embeds a line plot\n  - etc.\n\n  It also extends =shell-mode='s syntax highlighting, recenters and highlights\n  content for better viewing when you run commands like ~comint-previous-prompt~\n  and ~comint-kill-input~, and improves compatibility with evil-mode by\n  anticipating when to switch to insert mode.\n\n  Use =M-x shx RET= to start a new shell session with ~shx-mode~ enabled.\n\n  /This version is tested with Emacs 26.1/.  Check out the [[https://github.com/riscy/shx-for-emacs/releases][release log]].\n* Install\n*** From MELPA\n    =M-x package-install RET shx RET= to install =shx= from [[https://melpa.org/][MELPA]].\n*** From GNU Guix\n    =guix install emacs-shx= to install =shx= from [[https://guix.gnu.org/][GNU Guix]].\n*** Manually\n    Add the following to your =.emacs=:\n    #+begin_src elisp\n    (add-to-list 'load-path \"~/path/to/shx/\") ; add shx.el's directory to the load-path\n    (require 'shx)                            ; load shell-extras\n    #+end_src\n* Setup\n*** Quick-start\n    Type =M-x shx RET=.  Try out the following commands:\n\n    1. =:e ~/.bashrc= to edit your =.bashrc= (for example)\n    2. =:man ls= to display the man page for =ls=\n    3. =:help= to a start a completing read for other =shx= commands\n\n*** Enable automatically\n    If you like shx-mode, you can enable it everywhere:\n\n    #+begin_src elisp\n    (shx-global-mode 1)  ; toggle shx-mode on globally\n    #+end_src\n\n    Now shx will run automatically in any =comint-mode= buffer.  If you don't want\n    shx to run in every comint-mode buffer, you can use =M-x shx-mode= on a\n    case-by-case basis, or just add hooks to the mode in question, for example:\n\n    #+begin_src elisp\n    (add-hook 'inferior-python-mode-hook #'shx-mode)\n    #+end_src\n*** Customize\n    Use =M-x customize-group RET shx RET= to see shx's many customization options.\n    Here's an example customization using ~setq~:\n    #+begin_src elisp\n    (setq\n      ;; resync the shell's default-directory with Emacs on \"z\" commands:\n      shx-directory-tracker-regexp \"^z \"\n      ;; vastly improve display performance by breaking up long output lines\n      shx-max-output 1024\n      ;; prevent input longer than macOS's typeahead buffer from going through\n      shx-max-input 1024\n      ;; prefer inlined images and plots to have a height of 250 pixels\n      shx-img-height 250\n      ;; don't show any incidental hint messages about how to use shx\n      shx-show-hints nil\n      ;; flash the previous comint prompt for a full second when using C-c C-p\n      shx-flash-prompt-time 1.0\n      ;; use `#' to prefix shx commands instead of the default `:'\n      shx-leader \"#\")\n    #+end_src\n* Key bindings\n  | Key binding | Description                                                              |\n  |-------------+--------------------------------------------------------------------------|\n  | =C-RET=     | If the cursor is not on the prompt, paste the current line to the input  |\n  | =RET=       | If the cursor is on a filename or a URL, try to open it                  |\n  | =SPC=       | If the prompt is =:=, send =SPC= straight through to the process         |\n  | =q=         | If the prompt is =:=, send =q= straight through to the process           |\n\n  Note the prompt will be =:= when reading through the output of =less= or a =man= page\n  if you run the following:\n  #+begin_src elisp\n  (setenv \"LESS\" \"--dumb --prompt=s\")\n  #+end_src\n* Markup in the shell\n  shx's markup can enhance basic command-line applications and drive other\n  events.\n\n  If the output ever contains =\u003cview mountains.png\u003e= on a line by itself, then a\n  scaled rendering of =mountains.png= will be inlined within the text in the\n  shell.  This works because =view= is a shx command.  shx will execute any\n  (safe) shx command that appears with the following syntax:\n  #+begin_src xml\n  \u003ccommand arg1 arg2 ...\u003e\n  #+end_src\n  where ~command~ is a shx command and ~arg1 ... argn~ is a space-separated\n  list of arguments.  Arguments don't need to be surrounded by quotes -- the\n  command will figure out how to parse them.\n\n  You can use this markup to create a barplot (=:plotbar=) after collecting some\n  stats, or generate an =:alert= when a task is finished, and so forth.\n* Extra shell commands\n  shx's 'extra' commands are invoked by typing a =:= followed by the command's\n  name.  (You can change the =:= prefix by customizing the ~shx-leader~\n  variable.)  These commands are written in elisp and so can access all of\n  Emacs' facilities.  Type =:help= to see a complete listing of shx commands.\n\n  One command I use frequently is the =:edit= (shorthand =:e=) command:\n  #+begin_src bash\n  # edit the .emacs file:\n  :edit ~/.emacs\n\n  # use tramp to edit .emacs on a remote host through ssh:\n  :e /ssh:remote-host.com:~/.emacs\n\n  # use tramp to edit .bashrc on a running docker container:\n  :e /docker:02fbc948e009:~/.bashrc\n\n  # edit a local file as root\n  :sedit /etc/passwd\n  #+end_src\n\n  Thanks to [[https://github.com/CeleritasCelery][CeleritasCelery]] it's also possible to use environment variables in\n  the argument list:\n  #+begin_src bash\n  :e $HOME/.emacs.d\n  #+end_src\n  (To see an environment variable's value, use ~(getenv \"\u003cvar\u003e\")~.)\n\n  The =:ssh= and =:docker= commands are popular for opening \"remote\" shells:\n  #+begin_src bash\n  # open a shell on a remote host:\n  :ssh user@remote-host.com\n\n  # connect to a running docker container\n  :docker 8a8335d63ff3\n\n  # reopen the shell on the localhost:\n  :ssh\n  #+end_src\n  [[https://github.com/p3r7][Jordan Besly]] points out that you can customize the default interpreter\n  for each \"remote\" using [[https://www.gnu.org/software/emacs/manual/html_node/tramp/Remote-processes.html][connection-profile-set-local-variables]].\n\n  I also use the =:kept= and =:keep= commands frequently:\n  #+begin_src bash\n  # write a complicated command:\n  wget https://bootstrap.pypa.io/get-pip.py \u0026\u0026 python get-pip.py\n\n  # save the last command:\n  :keep\n\n  # search for commands having to do with pip:\n  :kept pip\n  #+end_src\n\n  Because these commands are written in elisp, shx gives =M-x shell= a lot of\n  the same advantages as =eshell=.  You can even evaluate elisp code directly in\n  the buffer (see =:help eval=).\n\n*** General commands\n    | Command              | Description                                           |\n    |----------------------+-------------------------------------------------------|\n    | =:alert=             | Reveal the buffer with an alert.  Useful for markup   |\n    | =:clear=             | Clear the buffer                                      |\n    | =:date=              | Show the date (even when the process is blocked)      |\n    | =:diff file1 file2=  | Launch an Emacs diff between two files                |\n    | =:edit file=         | Edit a file.  Shortcut: =:e \u003cfile\u003e=                   |\n    | =:eval (elisp-sexp)= | Evaluate some elisp code.  Example: =:eval (pwd)=     |\n    | =:find \u003cfilename\u003e=   | Run a fuzzy-find for \u003cfilename\u003e                       |\n    | =:goto-url \u003curl\u003e=    | Completing-read for a URL                             |\n    | =:header New header= | Change the current ~header-line-format~               |\n    | =:kept regexp=       | Show a list of your 'kept' commands matching regexp   |\n    | =:keep=              | Add the previous command to the list of kept commands |\n    | =:man topic=         | Invoke the Emacs man page browser on a topic          |\n    | =:ssh \u003chost\u003e=        | Restart the shell on the specified host               |\n\n    There are more than this -- type =:help= for a listing of all user commands.\n*** Graphical commands\n    | Command                      | Description            |\n    |------------------------------+------------------------|\n    | =:view image_file.jpg=       | Display an image       |\n    | =:plotbar data_file.txt=     | Display a bar plot     |\n    | =:plotline data_file.txt=    | Display a line plot    |\n    | =:plotmatrix data_file.txt=  | Display a heatmap      |\n    | =:plotscatter data_file.txt= | Display a scatter plot |\n    | =:plot3d data_file.txt=      | Display a 3D plot      |\n\n    These are for displaying inline graphics and plots in the shell buffer.  You\n    can control how much vertical space an inline image occupies by customizing\n    the ~shx-img-height~ variable.\n\n    Note =convert= (i.e. ImageMagick) and =gnuplot= need to be installed.  If\n    the binaries are installed but these commands aren't working, customize the\n    ~shx-path-to-convert~ and ~shx-path-to-gnuplot~ variables to point to the\n    binaries.  Also note these graphical commands aren't yet compatible with\n    shells launched on remote hosts (e.g. over ssh or in a Docker container).\n*** Asynchronous commands\n    | Command                           | Description                                       |\n    |-----------------------------------+---------------------------------------------------|\n    | =:delay \u003csec\u003e \u003ccommand\u003e=          | Run a shell command after a specific delay        |\n    | =:pulse \u003csec\u003e \u003ccommand\u003e=          | Repeat a shell command forever with a given delay |\n    | =:repeat \u003ccount\u003e \u003csec\u003e \u003ccommand\u003e= | Repeat a shell command ~\u003ccount\u003e~ times            |\n    | =:stop \u003cnum\u003e=                     | Cancel a repeating or delayed command             |\n\n    Use these to delay, pulse, or repeat a command a specific number of times.\n    Unfortunately these only support your typical shell commands, and not shx's\n    extra (colon-prefixed) commands.  So this possible:\n    #+begin_src bash\n    # Run the 'pwd' command 10 seconds from now:\n    :delay 10 pwd\n    #+end_src\n    But this is not possible:\n    #+begin_src bash\n    # Run the 'pwd' shx command 10 seconds from now (DOES NOT WORK)\n    :delay 10 :pwd\n    #+end_src\n*** Adding new commands\n    New shx commands are written by defining single-argument elisp functions\n    named ~shx-cmd-COMMAND-NAME~, where ~COMMAND-NAME~ is what the user would\n    type to invoke it.\n***** Example: a command to rename the buffer\n    If you evaluate the following (or add it to your ~.emacs~),\n    #+begin_src elisp\n    (defun shx-cmd-rename (name)\n      \"(SAFE) Rename the current buffer to NAME.\"\n      (if (not (ignore-errors (rename-buffer name)))\n          (shx-insert 'error \"Can't rename buffer.\")\n        (shx-insert \"Renaming buffer to \" name \"\\n\")\n        (shx--hint \"Emacs won't save buffers starting with *\")))\n    #+end_src\n    then each shx buffer will immediately have access to the =:rename= command.\n    When it's invoked, shx will also display a hint about buffer names.\n\n    Note the importance of defining a docstring.  This documents the\n    command so that typing =:help rename= will give the user information on what\n    the command does.  Further, since the docstring begins with =(SAFE)=,\n    it becomes part of shx's markup language.  So in this case if:\n    #+begin_src xml\n    \u003crename A new name for the buffer\u003e\n    #+end_src\n    appears on a line by itself in the output, the buffer will try to\n    automatically rename itself.\n***** Example: invoking ediff from the shell\n      A command similar to this one is built into shx:\n      #+begin_src elisp\n      (defun shx-cmd-diff (files)\n        \"(SAFE) Launch an Emacs `ediff' between FILES.\"\n        (setq files (shx-tokenize files))\n        (if (not (eq (length files) 2))\n            (shx-insert 'error \"diff \u003cfile1\u003e \u003cfile2\u003e\\n\")\n          (shx-insert \"invoking ediff...\\n\")\n          (shx--asynch-funcall #'ediff (mapcar #'expand-file-name files))))\n      #+end_src\n      Note that ~files~ is supplied as a string, but it's immediately parsed\n      into a list of strings using ~shx-tokenize~.  Helpfully, this function is\n      able to parse various styles of quoting and escaping, for example\n      ~(shx-tokenize \"'file one' file\\\\ two\")~\n      evaluates to\n      ~(\"file one\" \"file two\")~.\n***** Example: a command to browse URLs\n      If you execute the following,\n      #+begin_src elisp\n      (defun shx-cmd-browse (url)\n        \"Browse the supplied URL.\"\n        (shx-insert \"Browsing \" 'font-lock-keyword-face url)\n        (browse-url url))\n      #+end_src\n      then each shx buffer will have access to the =:browse= command.\n\n      Note the docstring does not specify that this command is =SAFE=.\n      This means =\u003cbrowse url\u003e= will not become part of shx's markup.  That\n      makes sense in this case, since you wouldn't want to give a process the\n      power to open arbitrary URLs without prompting.\n* Related\n  If you're here, these might be interesting:\n  - [[https://www.masteringemacs.org/article/shell-comint-secrets-history-commands][Shell \u0026 Comint Secrets: History commands]]\n  - [[https://www.masteringemacs.org/article/pcomplete-context-sensitive-completion-emacs][PComplete: Context-Sensitive Completion in Emacs]]\n  - [[https://dev.to/_darrenburns/10-tools-to-power-up-your-command-line-4id4][10 tools to power up your command line]]\n  - [[https://www.booleanworld.com/customizing-coloring-bash-prompt/][Creating dynamic bash prompts]]\n  - [[https://github.com/Orkohunter/keep][The Keep Utility]] inspired the =kept= and =keep= commands\n  - [[https://terminalsare.sexy/][\"Terminals Are Sexy\"]] (portal)\n  - [[https://github.com/riscy/command_line_lint][Command-Line Lint]], another project I maintain\n  - [[http://ohmyz.sh/][oh my zsh]], a community-driven zsh configuration\n  - [[https://github.com/Bash-it/bash-it][bash-it]], a community driven bash configuration\n\n  And if running a =dumb= terminal in Emacs isn't for you, here are some\n  alternatives:\n  - [[https://leanpub.com/the-tao-of-tmux/read][The Tao of tmux]], re: working in the terminal with tmux\n  - [[https://github.com/zsh-users/zsh-syntax-highlighting][zsh-syntax-highlighting]]\n  - [[https://hackernoon.com/macbook-my-command-line-utilities-f8a121c3b019#.clz934ly3][Shell configuration tips]] from Vitaly Belman\n  - [[http://www.iterm2.com/documentation-shell-integration.html][Shell integration]] for iTerm2 on macOS\n  - [[https://getbitbar.com/][BitBar]] adds program output to menus on macOS\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friscy%2Fshx-for-emacs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Friscy%2Fshx-for-emacs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Friscy%2Fshx-for-emacs/lists"}