Each day I find myself integrating more and more of my workflow into emacs. This time I realized that it would be nice to be able to run my applications without having to tab over to a terminal. It is not a very straight forward path to make this work, but I managed to scrap something together.
DISCLAIMER: This is a pretty loose way to do it. The elisp code I wrote isn't particularly good or hardened, so exercise caution.
Here is the code I wrote that will let me run a command in a general way. Ideally, I'll be able to hook into different major modes and supply them with relevant commands.
(defvar running-command "echo No running command configured!")
(defvar running-process nil)
(defvar running-process-buffer-name "*run application*")
(defun execute-run-command ()
(interactive)
(when (and (buffer-modified-p)
(y-or-n-p (format "Save file %s? " (buffer-file-name))))
(save-buffer))
(with-output-to-temp-buffer running-process-buffer-name
(setq running-process
(start-process-shell-command
"run application" running-process-buffer-name running-command))
(pop-to-buffer running-process-buffer-name)))
(defun kill-running-process ()
(interactive)
(kill-process running-process))
(global-set-key (kbd "<f8>") 'execute-run-command)
(global-set-key (kbd "C-S-<f8>") 'kill-running-process)
The running-command
is something configurable by major mode, and running-process
is a reference to the process we are running asynchronously.
I suspect, now that I write this, that my solitary reference to a running process is easily orphaned and some massive headaches will someday emerge. I'll cross that bridge when I come to it.
It's helpful to prompt to save open buffers to save me some unnecessary frustration. The shell command gets started asynchronously so that the standard output can be properly shown in the "run application" buffer as it arrives.
I didn't really have a good keybinding in mind, so I chose to attach it to the F8
key. M-<f8>
does something perplexing in OS X, and rather than fight with the operating system I just gave up and made it C-S-<f8>
. One day I might change it to something else, but this should do for now.
Tips
The environment variables of the current instance of emacs is reflected in the child processes that it creates. You might need to use things like M-x getenv
or M-x setenv RET keyname RET value
to set anything you need when running processes.
If you find that it gets into a weird state, you might end up having to restart emacs. Otherwise, you can do something like C-x C-b
to view a list of processes, and kill the *run application*
buffer. It should prompt you to kill its associated process, to which you should reply with a hearty and enthusiastic yes RET
.
Running Node.js Applications
To run a Node.js application, add this elisp:
(defun npm-run ()
(concat "npm start " (file-at-git-root "") " --no-color"))
(add-hook 'js2-mode-hook
(lambda ()
(set (make-local-variable 'running-command)
(npm-run))))
It will find the dominating git repository, and invoke npm start
on that directory. Note that if you don't include the --no-color
option you'll get all of the ANSI color codes in your shell output. I'm sure there is a way to get the buffer to automatically filter them, but I wasn't getting anywhere with my google-fu. The closest I came was finding an elisp function that could do that on a whole buffer:
(require 'ansi-color)
(defun display-ansi-colors ()
(interactive)
(let ((inhibit-read-only t))
(ansi-color-apply-on-region (point-min) (point-max))))
There is probably a way to run this function incrementally as data comes in, but it is a little deeper than my current knowledge of elisp.
Onward
In the future I may decide to rebuild this functionality to not depend on your mode, but instead to do it by the git repository root. At that point, I can prompt for what start command you want to use and default to the last command. That way any file that belongs to the same repository can be run in a similar way, and it makes room for small variations between different applications. For example, I might choose to run my application with a specific environment variable for just that process.
Now that I've built all of these different pieces, there's also a strong chance that I could make a single keystroke to do the full workflow: lint, test, run, and open a browser. Not sure if my workflow necessitates such levels of automation, but it is fun to think about the possibilities.
Bonus Points
Since I am generally working with web servers, I often open browsers and point them to a port on localhost. Here's some code to make that happen:
(defvar default-local-browser-port "80")
(defun open-browser (port)
(interactive
(list (read-string (format "Open Browser to (default-local-browser-port: %s) http://localhost:" default-local-browser-port))))
(when (not (eq (length port) 0))
(setq default-local-browser-port port))
(shell-command
(concat "open -a 'Google Chrome.app' 'http://localhost:'"
default-local-browser-port)))
(global-set-key (kbd "<f7>") 'open-browser)
Pressing <f7>
will prompt you for a port, and then open a browser to a localhost URL on it. It will remember the last one you entered, so subsequent usage will not require retyping the same port number.