In this post, I’ll describe how to edit Chrome textareas with the Edwin text editor that comes built-in with MIT/GNU Scheme.
If you just want to see the end result, see the screenshot and video at the end of this post.
These instructions will also work with recent releases of the Opera browser (since the newer Chromium-based versions can run Chrome plugins). They may also work at some point with Firefox, when Mozilla implements the new WebExtensions API.
At a high level, the steps to edit Chrome textareas with Edwin are:
- Install a browser add-on
- Customize Edwin with a few hacks
- Write a shell script to make it easy to launch Edwin from the command line
- Run a local “edit server” that interacts with the browser add-on and launches Edwin
On This Page
Install the ‘Edit with Emacs’ add-on
Install the Edit with Emacs add-on from the Chrome Web Store.
Load some Edwin hacks
The default way to open Edwin is to run
$ mit-scheme --edit
This just launches an Edwin editor window. From there, you need to manually open files and edit them.
What we need is a way to launch Edwin and open a specific file automatically. Most editors you are familiar with already do this, e.g.,
$ vim /tmp/foo.txt
$ emacsclient /tmp/bar.txt
To be able to launch Edwin in this way, we need to hack a few procedures in the file
editor.scm in the MIT/GNU Scheme source and load them from the Edwin init file. We’ll tackle each of these tasks separately below.
To get Edwin to open a file on startup, we need to tweak three procedures in
editor.scm to accept and/or pass around filename arguments:
Here’s the code; you can just paste it into a file somewhere. For the purposes of this post we’ll call it
;;;; open-edwin-on-file.scm -- Rich's hacks to open Edwin on a specific file.
;;; These (minor) changes are all to the file `editor.scm'. They are
;;; all that is needed to allow Edwin to be opened on a specific file
;;; by adding a `filename' argument to the EDIT procedure.
(define (create-editor file . args)
(if (null? args)
(set! create-editor-args args)
(filename (if (file-exists? file)
(let ((name (and (not (null? args)) (car args))))
(let ((type (name->display-type name)))
(if (not type)
(error "Unknown display type name:" name))
(if (not (display-type/available? type))
(error "Requested display type unavailable:"
(if (null? args) '() (cdr args))))
(set! edwin-initialization #f)
(set! edwin-continuation #f)
(define (standard-editor-initialization #!optional filename)
(if (and (not init-file-loaded?)
(let ((filename (os/init-file-name)))
(if (file-exists? filename)
(load-edwin-file filename '(EDWIN) #t)))
(set! init-file-loaded? #t)
(let ((buffer (find-buffer initial-buffer-name))
(filename (if (not (default-object? filename))
((ref-command find-file) filename)
(if (and buffer
(and (not (ref-variable inhibit-startup-message))
"You are in an interaction window of the Edwin editor."
"Type `C-h' for help, or `C-h t' for a tutorial."
"`C-h m' will describe some commands."
"`C-h' means: hold down the Ctrl key and type `h'.")))))))
(define (edit file . args)
(error "edwin: Editor already running"))
(apply create-editor file args))
((not (null? args))
(error "edwin: Arguments ignored when re-entering editor" args))
=> (lambda (restart)
(set! edwin-continuation #f)
(set! editor-abort continuation)
(fluid-let ((editor-abort continuation)
(lambda (with-editor-ungrabbed operations)
(let ((message (cmdl-message/null)))
(bind-condition-handler (list condition-type:error)
(do ((thunks (let ((thunks editor-initial-threads))
(set! editor-initial-threads '())
(create-thread root-continuation (car thunks)))
`((START-CHILD ,(editor-start-child-cmdl with-editor-ungrabbed))
(CHILD-PORT ,(editor-child-cmdl-port (nearest-cmdl/port)))
Update your Edwin init file
Then, you’ll need to tweak your Edwin init file (also known as
~/.edwin) to load this file into Edwin’s environment on startup:
(load "/path/to/open-edwin-on-file.scm" '(edwin))
Write a shell script to make it easier launch Edwin from the command line
Now that the
EDIT procedure takes a filename argument, we can wrap this all up in a shell script that calls Edwin with the right arguments. There may be other ways to accomplish this than in the code shown below, but it works.
Note that the path to my local installation of MIT/GNU Scheme on Mac OS X is slightly tweaked from the official install location. What’s important is that Scheme is invoked using the right “band”, or image file. For more information, see the fine manual.
Take the code below and stick it somewhere on your
$PATH; on my machine it lives at
if [[ $(uname) == 'Darwin' ]]; then
if [[ $(uname) == 'Linux' ]]; then
echo $SCHEME_CODE > $F
$CMD --load $F
Install an edit server
Although the extension is called ‘Edit with Emacs’, it can be used with any text editor. You just need to be able to run a local “edit server” that generates the right inputs and outputs. Since Chrome extensions can’t launch apps directly, the extension running in the browser needs to act as a client to a locally running server, which will launch the app.
Since we want to launch Edwin, we’ll need to run a local edit server. Here’s the one that I use:
To get the server to launch Edwin, I save the gist somewhere as
editserver.psgi and run the following script (for more information on the environment variables and what they mean, see the comments in the gist):
EDITSERVER_CMD='edwin %s' \
screen -d -m `which plackup` -s Starman -p 9292 -a ~/Code/mathoms/editserver.psgi
The relevant bit for running Edwin is the
EDITSERVER_CMD environment variable, which we’ve set to run the
edwin script shown above.
Note that this server is written in Perl and requires you to install the
Plack modules. If you don’t like Perl or don’t know how to install Perl modules, there are other servers out there that should work for you, such as this one written in Python.
Once you’ve done everything above and gotten it working together, you should be able to click the “edit” button next to your browser textarea and start Edwin. It will look something like the following screenshot (which you saw at the beginning of this post):
If you prefer video, check out this short demo on YouTube.