aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--readme.org119
-rw-r--r--sps-mode.el253
3 files changed, 374 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..077ad28
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.elc
+test
diff --git a/readme.org b/readme.org
new file mode 100644
index 0000000..9f1fbdb
--- /dev/null
+++ b/readme.org
@@ -0,0 +1,119 @@
+* sps-mode
+
+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.
+
+Note that this is very young code and there are certain to be serious problems.
+
+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
+
+=sps-mode= has both [[http://en.wikipedia.org/wiki/Common_Lisp][Common Lisp]] and Emacs dependencies.
+
+- Common Lisp dependencies
+ - Your preferred [[http://en.wikipedia.org/wiki/Common_Lisp][Common Lisp]] implementation
+ - [[http://common-lisp.net/project/parenscript/][Parenscript]]
+
+You almost certainly want to use [[http://www.quicklisp.org/beta/][Quicklisp]] to install Parenscript.
+
+- Emacs dependencies
+ - [[http://common-lisp.net/project/slime/][SLIME]]
+ - [[https://github.com/skeeto/skewer-mode][Skewer]]
+ - [[https://github.com/magnars/s.el][s.el]]
+ - [[https://github.com/magnars/dash.el][dash.el]]
+
+My recommendation is to install SLIME via the [[https://github.com/quicklisp/quicklisp-slime-helper][Quicklisp SLIME Helper]] and
+the others through [[http://www.emacswiki.org/emacs/ELPA][ELPA]].
+
+** Setup
+
+To enable sps-mode in a SLIME buffer: =M-x sps-mode=.
+
+To have =lisp-mode=, =slime-mode=, and =sps-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
+ (s-ends-with? ".paren" buffer-file-name))
+ (unless (slime-connected-p)
+ (save-excursion (slime)))
+ (sps-mode +1))))
+#+END_SRC
+
+=slime-mode= must be active for both expansion and evaluation commands. For
+evaluation commands, =skewer-mode= is also required. (See below for a list of
+the available commands).
+
+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
+
+** 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:
+
+ - =sps-expand-sexp=
+ - =sps-expand-last-expression=
+ - =sps-expand-defun=
+ - =sps-expand-region=
+ - =sps-expand-buffer=
+ - =sps-expand-dwim=
+
+From within an expansion buffer you can press "x" to send the JavaScript to the
+browser.
+
+*** Code evaluation commands
+
+These commands first compile the Parenscript code to JavaScript and then
+immediately send to it the browser to be evaluated:
+
+ - =sps-eval-sexp=
+ - =sps-eval-last-expression=
+ - =sps-eval-defun=
+ - =sps-eval-region=
+ - =sps-eval-buffer=
+ - =sps-eval-dwim=
+
+** Keybindings
+
+Many programming modes, including most Skewer modes, use keybindings which
+overlap with SLIME's. To enable those bindings, thus shadowing SLIME's, you can
+use =(sps-shadow-slime-keybindings!)=.
+
+However /this isn't recommended/. There's a good chance you'll want the
+flexibility to evaluate the code in an =sps-mode= buffer as either Common Lisp
+or Parenscript, in which case you won't want to shadow the SLIME keybindings.
+In this case you should assign key bindings yourself.
+
+** Still do be done
+
+ - Test against a wider array of code. Are there problems with quoting?
+ - Better documentation.
+ - Add a REPL and/or a scratch buffer.
+ - See if more integration with SLIME is possible (e.g. the selector).
+ - Command(s) for compiling with the output going to a file.
+ - Similar support for [[http://weitz.de/cl-who/][CL-WHO]] and/or [[https://github.com/paddymul/css-lite][CSS-LITE]]?
+ - Optional default keybindings that don't shadow SLIME.
+ - Function(s) to add keybindings behind prefix keys (see js2r).
+ - Get to know ELPA and packaging.
+ - Add support for Customize.
+
+** 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.
diff --git a/sps-mode.el b/sps-mode.el
new file mode 100644
index 0000000..4ee062a
--- /dev/null
+++ b/sps-mode.el
@@ -0,0 +1,253 @@
+;;; sps-mode.el --- Tools for interactive Parenscript development
+
+;; Copyright (C) 2013 John Mastro
+
+;; Author: John Mastro <john.b.mastro@gmail.com>
+;; Version: 0.0.1
+;; Keywords: languages, lisp, processes, tools
+;; Package-Requires: ((slime "2013-05-26") (skewer-mode "1.5.0")
+;; (s "1.6.1") (dash "1.0.3"))
+
+;;; Commentary:
+
+;; This is an Emacs minor mode and collection of commands for working with
+;; Parenscript code in SLIME and sending it to the browser via Skewer. The goal
+;; is to create an environment for hacking Parenscript which fits as naturally
+;; as possible into the Lisp style of interactive development.
+
+;; See https://github.com/johnmastro/sps-mode for additional information.
+
+;; Note that this is very young code and there are certain to be serious
+;; problems.
+
+;;; Code:
+
+(require 'dash)
+(require 's)
+
+;;;; Vars
+
+(defvar sps-mode-map (make-sparse-keymap)
+ "Keymap for sps-mode.
+This keymap is initialized empty. To repurpose the keys SLIME
+uses for its normal code evaluation and expansion commands, call
+`sps-shadow-slime-keybindings!`. Of course, you can alternatively
+define your own keybindings in the usual way.")
+
+(defvar sps-expansion-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "q") 'quit-window)
+ (define-key map (kbd "x") 'sps-send-expanded-code)
+ map)
+ "Keymap for `sps-expansion-mode` buffers.")
+
+(defvar sps-expansion-major-mode 'javascript-mode
+ "The major mode to enable in expansion buffers.
+Note that a serious bug seems to be triggered when using js2-mode
+in expansion buffers. Avoid it for the time being unless you know
+what you're doing."
+ ;; It would be better to use js2-mode, especially since Skewer requires it
+ ;; anyway (why make people load two different JavaScript modes)? But, I've
+ ;; come across a bug somehow related to js2-mode and skewer - when an
+ ;; expansion is loaded in a js2-mode buffer the code is unexpectedly
+ ;; evaluated (even though we haven't called skewer-eval) and, worse, Emacs
+ ;; occasionally freezes. Until I track this down I don't want to leave the
+ ;; code laying around with js2-mode as the default in case anyone happens
+ ;; across it and gives it a try.
+ )
+
+;;;; A few utilities
+
+(defun sps-wrap-in-ps-form (string)
+ "Return Parenscript STRING wrapped in a PS:PS form."
+ (format "(ps:ps %s)" string))
+
+(defun sps-swank-eval-expr (string)
+ "Return a form appropriate for evaluating STRING via Swank."
+ `(swank:eval-and-grab-output ,(sps-wrap-in-ps-form string)))
+
+(defun sps-file-name-base (filename)
+ "Return FILENAME's basename without it's extension."
+ (file-name-sans-extension (file-name-nondirectory filename)))
+
+;;;; Code expansion
+
+(defun sps-expand (string)
+ "Display the JavaScript generated from Parenscript STRING.
+
+The resulting JavaScript is displayed in a temporary buffer. The
+buffer's major mode is determined by the variable
+`sps-expansion-major-mode` (`javascript-mode` by default).
+`sps-expansion-mode` is enabled as an additional minor mode."
+ (slime-eval-async (sps-swank-eval-expr string)
+ #'(lambda (result)
+ (let ((code (read (cadr result))))
+ (slime-with-popup-buffer ("*Parenscript generated JavaScript*")
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (insert code)
+ (funcall sps-expansion-major-mode)
+ (sps-expansion-mode 1)
+ (font-lock-fontify-buffer)
+ (goto-char (point-min))
+ (setq buffer-read-only t)
+ (pop-to-buffer (current-buffer)))))
+ (slime-current-package)))
+
+;; (defun sps-compile-buffer-to-file (&optional append)
+;; (interactive "p")
+;; (when (and (buffer-modified-p)
+;; (y-or-n-p "Save buffer? "))
+;; (save-buffer))
+;; (let* ((this buffer-file-name)
+;; (dir (and this (file-name-directory this)))
+;; (initial (and this (concat (sps-file-name-base this) ".js")))
+;; (destination (read-file-name "Destination: " dir nil nil initial nil))
+;; (string (buffer-substring-no-properties (point-min) (point-max))))
+;; (slime-eval-async (sps-swank-eval-expr string)
+;; #'(lambda (result)
+;; (let ((code (read (cadr result))))
+;; (write-region code nil destination append)))
+;; (slime-current-package))))
+
+(defun sps-expand-sexp ()
+ "Display the expansion of the form at point."
+ (interactive)
+ (sps-expand (slime-sexp-at-point)))
+
+(defun sps-expand-last-expression ()
+ "Display the expansion of the expression preceding point."
+ (interactive)
+ (sps-expand (slime-last-expression)))
+
+(defun sps-expand-defun ()
+ "Display the expansion of the current toplevel form."
+ (interactive)
+ (sps-expand (slime-defun-at-point)))
+
+(defun sps-expand-region (beg end)
+ "Display the expansion of the currently active region."
+ (interactive "r")
+ (sps-expand (buffer-substring-no-properties beg end)))
+
+(defun sps-expand-buffer ()
+ "Display the expansion of the current buffer."
+ (interactive)
+ (sps-expand-region (point-min) (point-max)))
+
+(defun sps-expand-dwim ()
+ "Display the expansion of the active region or toplevel form.
+If the region is active this is equivalent to invoking
+`sps-expand-region`, otherwise it's equivalent to
+`sps-expand-defun`."
+ (interactive)
+ (if (region-active-p)
+ (sps-expand-region (region-beginning) (region-end))
+ (sps-expand-defun)))
+
+;;;; Code evaluation
+
+(defun sps-eval (string)
+ "Compile Parenscript STRING and evaluate it in the browser.
+
+The code is first compiled to JavaScript in the CL image and then
+sent to the browser via `skewer-eval`."
+ (slime-eval-async (sps-swank-eval-expr string)
+ #'(lambda (result)
+ (let ((code (read (cadr result))))
+ (skewer-eval code #'skewer-post-minibuffer)))
+ (slime-current-package)))
+
+(defun sps-eval-sexp ()
+ "Evaluate the expression at point as Parenscript."
+ (interactive)
+ (sps-eval (slime-sexp-at-point)))
+
+(defun sps-eval-last-expression ()
+ "Evaluate the expression preceding point as Parenscript."
+ (interactive)
+ (sps-eval (slime-last-expression)))
+
+(defun sps-eval-defun ()
+ "Evaluate the current toplevel form as Parenscript."
+ (interactive)
+ (sps-eval (slime-defun-at-point)))
+
+(defun sps-eval-region (beg end)
+ "Evaluate the currently active region as Parenscript."
+ (interactive "r")
+ (sps-eval (buffer-substring-no-properties beg end)))
+
+(defun sps-eval-buffer ()
+ "Evaluate the current buffer as Parenscript."
+ (interactive)
+ (sps-eval-region (point-min) (point-max)))
+
+(defun sps-eval-dwim ()
+ "Evaluate the active region or toplevel form.
+If the region is active this is equivalent to invoking
+`sps-eval-region`, otherwise it's equivalent to
+`sps-eval-defun`."
+ (if (region-active-p)
+ (sps-eval-region (region-beginning) (region-end))
+ (sps-eval-defun)))
+
+;;;; Keybindings
+
+(defun sps-shadow-slime-keybindings! ()
+ "Rebind SLIME's evaluation keys for Parenscript.
+
+This uses the same keys that `slime-mode` uses, thus shadowing
+them. If you want to use both sets of evaluation commands in the
+same buffer you will likely want to define other keys manually.
+
+This command must be run in the buffer that the keybindings are
+to be used in - it relies on `make-local-variable` to avoid
+changing SLIME's bindings in other buffers."
+ (interactive)
+ (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 RET") nil))
+ (let ((map sps-mode-map))
+ (define-key map (kbd "C-x C-e") 'sps-eval-last-expression)
+ (define-key map (kbd "C-c C-r") 'sps-eval-region)
+ (define-key map (kbd "C-M-x") 'sps-eval-defun)
+ (define-key map (kbd "C-c C-k") 'sps-eval-buffer)
+ (define-key map (kbd "C-c RET") 'sps-expand-sexp)))
+
+;;;; The minor mode
+
+;;;###autoload
+(define-minor-mode sps-mode
+ "Minor mode for interactively evaluating Parenscript forms."
+ :lighter " sps"
+ :keymap sps-mode-map)
+
+;;;; Expansion minor mode
+
+(defun sps-send-expanded-code ()
+ "Send the expanded code to the browser."
+ (interactive)
+ (skewer-eval
+ (buffer-substring-no-properties (point-min) (point-max))
+ #'skewer-post-minibuffer))
+
+(define-minor-mode sps-expansion-mode
+ "Minor mode for displaying the code generated by Parenscript."
+ :lighter nil
+ :keymap sps-expansion-mode-map)
+
+
+(provide 'sps-mode)
+
+;; Local Variables:
+;; lexical-binding: t
+;; indent-tabs-mode: nil
+;; coding: utf-8
+;; End:
+
+;;; sps-mode.el ends here