blob: ffd8cabadb211d7b29da02665988819d7f5197e1 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
|
;;; 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. You can optionally use
`sps-add-keys-with-prefix` to add bindings for all commands on
two-key sequences behind a prefix of your choice.")
(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-scratch-mode-map (make-sparse-keymap)
"Keymap for the *sps-scratch* buffer.")
(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)))
(defun sps-send-expanded-code ()
"Send the expanded code to the browser.
For use from `sps-expansion-mode` buffers."
(interactive)
(skewer-eval
(buffer-substring-no-properties (point-min) (point-max))
#'skewer-post-minibuffer))
;;;; Keybindings
(defun sps-prefix-keys (prefix keys)
"Prepend PREFIX to KEYS and read with `read-kbd-macro`."
(read-kbd-macro (concat prefix " " keys)))
(defun sps-add-keys-with-prefix (p)
"Add keybindings for `sps-mode` commands behind prefix P."
(let ((map sps-mode-map))
;; Evaluation commands
(define-key map (sps-prefix-keys p "e C-m") 'sps-eval-sexp)
(define-key map (sps-prefix-keys p "ee") 'sps-eval-last-expression)
(define-key map (sps-prefix-keys p "ed") 'sps-eval-defun)
(define-key map (sps-prefix-keys p "er") 'sps-eval-region)
(define-key map (sps-prefix-keys p "eb") 'sps-eval-buffer)
(define-key map (sps-prefix-keys p "e SPC") 'sps-eval-dwim)
;; Expansion commands
(define-key map (sps-prefix-keys p "x C-m") 'sps-expand-sexp)
(define-key map (sps-prefix-keys p "xe") 'sps-expand-last-expression)
(define-key map (sps-prefix-keys p "xd") 'sps-expand-defun)
(define-key map (sps-prefix-keys p "xr") 'sps-expand-region)
(define-key map (sps-prefix-keys p "xb") 'sps-expand-buffer)
(define-key map (sps-prefix-keys p "x SPC") 'sps-expand-dwim)))
;;;; Scratch buffer
(defun sps-scratch-buffer ()
"Return the scratch buffer, creating it if necessary."
(let ((name "*sps-scratch*"))
(or (get-buffer name)
(with-current-buffer (get-buffer-create name)
(lisp-mode)
(slime-mode t)
(sps-mode 1)
(sps-scratch-mode 1)
(current-buffer)))))
(defun sps-switch-to-scratch-buffer ()
"Jump to the *sps-scratch* buffer."
(set-buffer (sps-scratch-buffer))
(unless (eq (current-buffer) (window-buffer))
(pop-to-buffer (current-buffer) t)))
(defun sps-scratch ()
"Jump to the *sps-scratch* buffer."
(interactive)
(sps-switch-to-scratch-buffer))
;;;; A little extra SLIME integration
(defun sps-mode-buffer-p (buffer)
"Return t if `sps-mode` is active in BUFFER."
(with-current-buffer buffer
(bound-and-true-p sps-mode)))
(defun sps-recently-visited-sps-buffer ()
"Return the most recently visited `sps-mode` buffer.
Only considers buffers that are not already visible."
(or (-first #'(lambda (b) (and (sps-mode-buffer-p b)
(null (get-buffer-window b 'visible))))
(buffer-list))
(error "Can't find unshown buffer in sps-mode")))
(defun sps-add-slime-selector-methods ()
"Add methods to `slime-selector` for `sps-mode` buffers.
Allows access to the most recently visited buffer with `sps-mode`
active via \"p\" and to the *sps-scratch* buffer via \"P\"."
(interactive)
(def-slime-selector-method ?p
"most recently visited buffer using sps-mode."
(sps-recently-visited-sps-buffer))
(def-slime-selector-method ?P
"*sps-scratch* buffer."
(sps-scratch-buffer)))
;;;; The minor modes
;;;###autoload
(define-minor-mode sps-mode
"Minor mode for interactively evaluating Parenscript forms."
:lighter " sps"
:keymap sps-mode-map)
;;;###autoload
(define-minor-mode sps-scratch-mode
"Mode for sps-mode scratch buffer."
:lighter nil
:keymap sps-scratch-mode-map)
(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
|