Clementson's Blog

Bits and pieces (mostly Lisp-related) that I collect from the ether.

April 2007
Sun Mon Tue Wed Thu Fri Sat
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
Mar  May

SLIME Tips and Techniques - Part 6 (Send SEXPs to REPL)

Tuesday, April 24, 2007

I sometimes do lispvan (or other) presentations where I want to single-step through code that is in a lisp file while discussing the code with the audience. Basically, I have the lisp code in one window while, in a second window, I evaluate the code and look at the output in a SLIME REPL. A typical presentation setup would be something similar to this:

SLIME Presentation

I have one window open on the left-hand side with the source code in it. In the right-hand window, I have a SLIME REPL. I move the cursor through the source code (in the left window) and press a function key to copy a specific s-expression to the SLIME REPL and evaluate it. This is more effective than just pressing "C-x C-e" as it isn't always apparent to the audience where the cursor is at in the source window. By copying the code over, there is the visual reinforcement as to what is happening and what code is being evaluated. In the past, I've used a simple keyboard macro to do this; however, it wasn't really flexible enough. So, I wrote an elisp function to do the job:

(defun slime-send-dwim (arg)
  "Send the appropriate forms to CL to be evaluated."
  (interactive "P")
  (save-excursion
    (cond 
      ;;Region selected - evaluate region
      ((not (equal mark-active nil))
       (copy-region-as-kill-nomark (mark) (point)))
      ;; At/before sexp - evaluate next sexp
      ((or (looking-at "\s(")
	   (save-excursion
	     (ignore-errors (forward-char 1))
	     (looking-at "\s(")))
       (forward-list 1)
       (let ((end (point))
	     (beg (save-excursion
		    (backward-list 1)
		    (point))))
	 (copy-region-as-kill-nomark beg end)))
      ;; At/after sexp - evaluate last sexp
      ((or (looking-at "\s)")
	   (save-excursion
	     (backward-char 1)
	     (looking-at "\s)")))
       (if (looking-at "\s)")
	   (forward-char 1))
       (let ((end (point))
	     (beg (save-excursion
		    (backward-list 1)
		    (point))))
	 (copy-region-as-kill-nomark beg end)))
      ;; Default - evaluate enclosing top-level sexp
      (t (progn
	   (while (ignore-errors (progn
				   (backward-up-list)
				   t)))
	   (forward-list 1)
	   (let ((end (point))
		 (beg (save-excursion
			(backward-list 1)
			(point))))
	     (copy-region-as-kill-nomark beg end)))))
    (set-buffer (slime-output-buffer))
    (unless (eq (current-buffer) (window-buffer))
      (pop-to-buffer (current-buffer) t))
    (goto-char (point-max))
    (yank)
    (if arg (progn
	      (slime-repl-return)
	      (other-window 1)))))
The function actually does a few things:
  1. If a region has been selected, the region will be copied to the SLIME REPL.
  2. If the point is either at an open paren or before an open paren, the following s-expression will be copied to the SLIME REPL.
  3. If the point is either at a close paren or after a close paren, the preceeding s-expression will be copied to the SLIME REPL.
  4. If none of the above conditions has been met, the top-level s-expression enclosing the point will be copied to the SLIME REPL.
Since the evaluation is done in the above order, it is easy to select the specific form that you want to send to the REPL. I typically bind the function to a function key for the demo. Although the default action is to just copy the s-expression to the REPL (positioning the point at the end of the s-expression so that the user can just press Enter to evaluate the form), if the function is called with a prefix argument, it will also "press Enter" in the REPL buffer and the cursor will stay in the source window. This makes it pretty convenient when you're stepping through a lot of forms in a demo. So, I typically set up bindings for both variations just for the presentation:
(define-key lisp-mode-map [f7] 'slime-send-dwim)
(define-key lisp-mode-map [f8] '(lambda ()
				  (interactive)
				  (slime-send-dwim 1)))
Although I use slime-send-dwim primarily for presentations, some people may also find it useful for just sending forms to the REPL (which has the added advantage over using normal SLIME eval/compile commands in that it will update the history and */**/*** values in the REPL).

Update-2007-04-28: Vilson Vieira da Silva pointed out to me in an email that the copy-region-as-kill-nomark function that I use in slime-send-dwim is in the pc-select package and that a "(require 'pc-select)" is needed. Alternatively, if you don't want to use pc-select, you can just substitute copy-region-as-kill for copy-region-as-kill-nomark.

emacs Copyright © 2007 by Bill Clementson