johnmastro / trident-mode.el

Emacs minor mode for live Parenscript interaction
The Unlicense
75 stars 8 forks source link

This is an [[http://www.gnu.org/software/emacs/][Emacs]] minor mode and collection of commands for working with [[http://common-lisp.net/project/parenscript/][Parenscript]] code in [[http://common-lisp.net/project/slime/][SLIME]] and sending it to the browser via [[https://github.com/skeeto/skewer-mode][Skewer]]. The goal is to create an environment for hacking Parenscript which fits as naturally as possible into the Lisp style of interactive development.

There's at least one other project with related goals, [[https://github.com/3b/slime-proxy][slime-proxy]], though at the time of writing it's unclear whether it's still being actively developed.

** Installation

Trident is available on [[http://melpa.milkbox.net/][MELPA]], meaning a simple =M-x package-install RET trident-mode RET= will install both it and its dependencies.

To enable MELPA, if you haven't already, add something like the following to your Emacs configuration:

+BEGIN_SRC emacs-lisp

(require 'package) (add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/") t) (package-initialize)

+END_SRC

The dependencies that will be installed are:

Trident also requires a [[http://en.wikipedia.org/wiki/Common_Lisp][Common Lisp]] implementation and Parenscript. [[http://www.quicklisp.org/beta/][Quicklisp]] is the best way to install Parenscript.

** Setup

To enable =trident-mode= in a SLIME buffer: =M-x trident-mode=.

To have =lisp-mode=, =slime-mode=, and =trident-mode= all enable automatically for any file with an extension of ".paren":

+BEGIN_SRC emacs-lisp

(add-to-list 'auto-mode-alist (cons "\.paren\'" 'lisp-mode)) (add-hook 'lisp-mode-hook

'(lambda ()

            (when (and buffer-file-name
                       (string-match-p "\\.paren\\>" buffer-file-name))
              (unless (slime-connected-p)
                (save-excursion (slime)))
              (trident-mode +1))))

+END_SRC

Parenscript must be loaded in your Common Lisp image, and you'll probably also want to import its symbols:

+BEGIN_SRC common-lisp

(ql:quickload :parenscript) (use-package :parenscript)

+END_SRC

With the above taken care of it's time to skewer the browser. See [[https://github.com/skeeto/skewer-mode/blob/master/README.md][Skewer's README]] for detailed information on the multiple ways you can connect to a site - including sites on servers you don't control.

The fastest way to simply try things out is to run =M-x run-skewer=. Skewer will load an empty page in your browser and connect to it. You can immediately begin using Trident's evaluation commands (described below); to additionally open a JavaScript REPL you can run =M-x skewer-repl=.

** Commands

*** Code expansion commands

These commands generate JavaScript from the Parenscript code and display it but don't send it to the browser for evaluation:

From within an expansion buffer you can press =e= to send the JavaScript to the browser, =w= to copy it to the kill ring, =s= to save it to a file (you'll be prompted for the destination) or =q= to dismiss the buffer. The copy command, =w=, acts on the region if it's active or the entire buffer otherwise.

Additionally, you can use =M-x trident-compile-buffer-to-file= to expand the current buffer and save the generated code directly to a file.

*** Code evaluation commands

These commands first compile the Parenscript code to JavaScript and then immediately send to it the browser to be evaluated:

** Key bindings

The traditional set of code evaluation key bindings is a poor fit for Trident, since they would shadow SLIME's equivalent commands and that's probably not what you want. That leaves us without a clear convention to follow, so by default we don't establish any key bindings at all. However, the function =trident-add-keys-with-prefix= will add two-key key bindings for all commands behind a prefix of your choice.

For example:

+BEGIN_SRC emacs-lisp

(trident-add-keys-with-prefix "C-c C-e") ;; The key sequence for trident-eval-region is "e r", so it's now bound to "C-c ;; C-e er"

+END_SRC

The full list of key bindings =trident-add-keys-with-prefix= will establish is:

Evaluation commands begin with an "e", expansion commands with "x". The second letter is generally mnemonic but not always. The =-sexp= commands use =RET= in correspondence to =slime-expand-1=, and the =-dwim= commands use the space bar because it's easy and comfortable to hit.

Please consider these keys provisional, and let me know if you have any ideas for improving the arrangement.

If you really want to shadow SLIME's key bindings in buffers where =trident-mode= is active you could do something like this:

+BEGIN_SRC emacs-lisp

(defun steal-slime-keys-for-trident! () ;; Don't affect all SLIME buffers, just where invoked (make-local-variable 'slime-mode-map) (let ((map slime-mode-map)) (define-key map (kbd "C-x C-e") nil) (define-key map (kbd "C-c C-r") nil) (define-key map (kbd "C-M-x") nil) (define-key map (kbd "C-c C-k") nil) (define-key map (kbd "C-c C-m") nil)) (let ((map trident-mode-map)) (define-key map (kbd "C-x C-e") 'trident-eval-last-expression) (define-key map (kbd "C-c C-r") 'trident-eval-region) (define-key map (kbd "C-M-x") 'trident-eval-defun) (define-key map (kbd "C-c C-k") 'trident-eval-buffer) (define-key map (kbd "C-c C-m") 'trident-expand-sexp)))

(add-hook 'trident-mode-hook 'steal-slime-keys-for-trident!)

+END_SRC

** Other amenities

=slime-selector= is a great feature and Trident can optionally integrate with it. If you call =trident-add-slime-selector-methods=, two entries related to =trident-mode= will be added. One, invoked with =p=, will take you to the most recently visited buffer where =trident-mode= is active (excluding buffers which are already visible). The other, on =P=, will take you to a scratch buffer with =trident-mode= enabled, creating the buffer if necessary.

Speaking of the scratch buffer, the =trident-scratch= command will take you straight there.

** Still do be done

** Contributing

Contributions are very welcome. Since I've just started working on this and don't have everything figured out yet, please first contact me on GitHub or send me an email so we can talk before you start working on something.