diff options
Diffstat (limited to 'sps-mode.el')
-rw-r--r-- | sps-mode.el | 253 |
1 files changed, 253 insertions, 0 deletions
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 |