Emacs, the text editor where you can read mail and play Tetris, is often cast in opposition to the Unix philosophy of "do one thing well" and "combine programs". It's a false dichotomy. Emacs can do a lot on its own and can be combined usefully with other programs.
run-command
makes the combination convenient through a simple configuration format and an interaction flow that stays out of your way. Where you'd usually reach for a shell or look for a specialized major mode, with run-command
you can write a short recipe and get a command that is easy to bring up, invoke, and keep track of, without leaving Emacs.
Table of Contents
- Demo
- Features
- Installation
- Quickstart
- Invocation
- Configuration
- Tutorial: adding commands
- Generating commands on the fly
The screencast below shows using run-command
to 1) clone a project from a boilerplate, 2) execute a file on every save, and 3) start the test runner.
- Unopinionated: run a command against a word, a file, a project, a service, anything.
- Minimal cognitive tax: one key binding, one configuration variable.
- Flexible configuration: hardcode commands, generate them dynamically based on context, or anything in between.
- Add a "command recipe" to your init file, for example:
(defun run-command-recipe-example ()
(list
;; Arbitrary command
(list :command-name "say-hello"
:command-line "echo Hello, World!")
;; Do something with current directory
(list :command-name "serve-http-dir"
:command-line "python3 -m http.server 8000"
:display "Serve current directory over HTTP")
;; Do something with current file if it's a README
;; (uses https://github.com/joeyespo/grip)
(when (equal (buffer-name) "README.md")
(list :command-name "preview-github-readme"
:command-line "grip --browser --norefresh"
:display "Preview GitHub README"))
;; Do something with current file if it's executable
(when-let* ((buffer-file (buffer-file-name))
(executable-p (and buffer-file (file-executable-p buffer-file))))
(list :command-name "run-buffer-file"
:command-line buffer-file
:display "Run this buffer's file"))
;; Do something with current word
;; (uses https://wordnet.princeton.edu/documentation/wn1wn)
(when-let ((word (thing-at-point 'word t)))
(list :command-name "wordnet-synonyms"
:command-line (format "wn '%s' -synsn -synsv -synsa -synsr" word)
:display (format "Look up '%s' synonyms in wordnet" word)))))
-
Customize
run-command-recipes
and addrun-command-recipe-example
to the list. -
Type
M-x run-command RET
.
Read more about invocation, configuration, and how to add commands, or check out some recipe examples.
Type M-x run-command
or bind run-command
to a key:
(global-set-key (kbd "C-c c") 'run-command)
Or:
(use-package run-command
:bind ("C-c c" . run-command)
When completing via Helm or Ivy, you can edit a command before running it by typing C-u RET
instead of RET
.
Write recipe functions into your init file as described in the quickstart and the tutorial, then add them via M-x customize
to the run-command-recipes
variable. This is the only required configuration.
By default, commands are run in compilation-mode
. See Lightweight external command integration in Emacs via compilation mode for some notes on how to make the most of compilation-mode
. An alternative method (and probably future default) is term-mode
plus compilation-minor-mode
, especially useful for commands with rich output such as colors, progress bars, and screen refreshes, while preserving compilation-mode
functionality. Set run-command-run-method
to term
and please comment on issue #2 if you find issues.
The auto-completion framework is automatically detected. It can be set manually by customizing run-command-completion-method
.
To provide a more user-friendly name for a command, use the :display
property:
(defun run-command-recipe-example ()
(list
(list :command-name "serve-http-dir"
:command-line "python3 -m http.server 8000"
:display "Serve directory over HTTP port 8000")))
A command runs by default in the current buffer's directory. You can make it run in a different directory by setting :working-dir
.
For example, you want to serve the current directory via HTTP, unless you're visiting a file that is somewhere below a public_html
directory, in which case you want to serve public_html
instead:
(defun run-command-recipe-example ()
(list
(list :command-name "serve-http-dir"
:command-line "python3 -m http.server 8000"
:display "Serve directory over HTTP port 8000"
:working-dir (let ((project-dir
(locate-dominating-file default-directory "public_html")))
(if project-dir
(concat project-dir "public_html")
default-directory)))))
See the Hugo project recipe for a recipe that uses the project's directory for all commands.
To disable a command in certain circumstances, return nil
in its place.
For example, you want to enable a command only when the current buffer is visiting an executable file:
(defun run-command-recipe-example ()
(let ((buffer-file (buffer-file-name)))
(list
(when (and buffer-file (file-executable-p buffer-file))
(list
:command-name "run-buffer-file"
:command-line buffer-file
:display "Run this buffer's file")))))
See the executable file recipe for a variant that also re-runs the file on each save.
See the Hugo project recipe for a recipe that switches off entirely when you're not in a Hugo project.
You can name a recipe function anything. If the name begins with run-command-recipe-
, that will be removed when displaying commands.
For example:
(defun run-command-recipe-example ()) ;; displays as "example"
(defun my-command-recipe ()) ;; displays as "my-command-recipe"
Recipes are plain old Lisp functions, so they generate commands based on e.g. project setup.
See the NPM project recipe, which uses a JavaScript's project package.json
file to generate commands, and the Make project recipe, which does the same for Makefile
projects.