Hi Slime-hackers,
I implemented the macroexpansion stack that I talked about several month
ago. With the attached patch, it's possible to press `l' (for "last",
like in the inspector) to get to the same content as of the previous
macroexpansion buffer, for example, after an in-place expansion. It also
tries to use the highlighting-edits facility in the most reasonable way
I could make out.
It's cool, man! Try it out.
-T.
2006-05-11 Tobias Rittweiler <PUT-MY-ADDRESS-HERE>
* slime.el (slime-use-autodoc-mode): Fix typo in docstring.
* slime.el (slime-use-highlight-edits-mode): New variable,
analogous to SLIME-USE-AUTODOC-MODE.
(slime-setup, slime-lisp-mode-hook): Make above variable
work. Also, activates the HIGHLIGHT-EDITS-MODE in proper way (thus
avoiding the nasty "Toggling ... off; better pass an explicit
argument." message.)
* slime.el (with-struct-slots): Renamed from WITH-STRUCT. Also,
changed calling semantics slightly.
* slime.el (slime-get-temp-buffer-create): Added new keyword
ACTIVATOR that represents a function responsible for activating a
given buffer, supersedes the NOSELECTP keyword which got removed
because it wasn't used anywhere. Updated docstring.
(slime-with-output-to-temp-buffer): Likewise.
* slime.el: Fix typo in comment about communication protocol.
* slime.el (slime-macroexpansion-buffer-name): New variable,
contains the name of the macroexpansion buffer.
* slime.el: Implemented macroexpansion stack, for instance, for
going back from an in-place macroexpansion to its originated
expansion:
(slime-macroexpansion-minor-mode): Pressing `l' inside the
macroexpansion buffer, will bring the user back to the previous
macroexpansion, if possible.
(slime-macroexpansion-stack): New variable, the stack.
(slime-macroexpansion-struct): Structure representing one item in
the stack, an item contains all relevant information to restore
the macroexpansion buffer to its prior appearance.
(slime-eval-macroexpand-expression): Removed, because the same
information is now stored in the stack items.
(slime-macroexpansion-kill-buffer-finalizer): Responsible for
clearing the stack, when the macroexpansion buffer is killed.
(slime-update-macroexpansion-stack-top): New.
(slime-extend-macroexpansion-stack): New.
(with-macroexpansion-buffer): New macro, responsible for finding
respectively creating a macroexpansion buffer that's made the
current buffer within its scope.
(slime-eval-macroexpand, slime-eval-macroexpand-inplace):
Accomodated for the new macroexpansion stack.
(slime-macroexpansion-buffer-go-back): New function, responsible
for restoring the previous macroexpansion in the stack.
(slime-macroexpand-again): Accomodated to macroexpansion stack.
--- /home/tcr/src/from-upstream/slime/slime.el 2006-04-15 15:46:14.000000000 +0200
+++ slime.el 2006-05-11 18:09:30.000000000 +0200
@@ -65,7 +65,10 @@
(require 'easymenu)
(defvar slime-use-autodoc-mode nil
- "When non-nil always enabled slime-autodoc-mode in slime-mode.")
+ "When non-nil always enable slime-autodoc-mode in slime-mode.")
+
+(defvar slime-use-highlight-edits-mode nil
+ "When non-nil always enable slime-highlight-edits-mode in slime-mode")
(defun* slime-setup (&key autodoc typeout-frame highlight-edits)
"Setup Emacs so that lisp-mode buffers always use SLIME."
@@ -73,15 +76,16 @@
(when typeout-frame
(add-hook 'slime-connected-hook 'slime-ensure-typeout-frame))
(setq slime-use-autodoc-mode autodoc)
- (when highlight-edits
- (add-hook 'slime-mode-hook 'slime-highlight-edits-mode)))
+ (setq slime-use-highlight-edits-mode highlight-edits))
(defun slime-lisp-mode-hook ()
(slime-mode 1)
(set (make-local-variable 'lisp-indent-function)
'common-lisp-indent-function)
(when slime-use-autodoc-mode
- (slime-autodoc-mode 1)))
+ (slime-autodoc-mode 1))
+ (when slime-use-highlight-edits-mode
+ (slime-highlight-edits-mode 1)))
(eval-and-compile
(defvar slime-path
@@ -1003,9 +1007,10 @@
(put 'slime-define-keys 'lisp-indent-function 1)
-(defmacro* with-struct ((conc-name &rest slots) struct &body body)
- "Like with-slots but works only for structs.
-\(fn (CONC-NAME &rest SLOTS) STRUCT &body BODY)"
+(defmacro* with-struct-slots ((&rest slots) (struct &key conc-name) &body body)
+ "Similiar to Common Lisp's WITH-SLOTS but works only for
+structs. CONC-NAME is prepended to each slot-name in SLOTS to
+access the respective field within STRUCT."
(flet ((reader (slot) (intern (concat (symbol-name conc-name)
(symbol-name slot)))))
(let ((struct-var (gensym "struct")))
@@ -1019,7 +1024,7 @@
slots)
. ,body)))))
-(put 'with-struct 'lisp-indent-function 2)
+(put 'with-struct-slots 'lisp-indent-function 2)
;;;;; Very-commonly-used functions
@@ -1166,35 +1171,43 @@
"The window config \"fingerprint\" after displaying the buffer."))
;; Interface
-(defun* slime-get-temp-buffer-create (name &key mode noselectp)
+(defun* slime-get-temp-buffer-create (name &key mode activator)
"Return a fresh temporary buffer called NAME in MODE.
The buffer also uses the minor-mode `slime-temp-buffer-mode'. Pressing
`q' in the buffer will restore the window configuration to the way it
is when the buffer was created, i.e. when this function was called.
-If NOSELECTP is true then the buffer is shown by `display-buffer',
-otherwise it is shown and selected by `pop-to-buffer'."
+ACTIVATOR is a function that is responsible for activating
+(i.e. showing, selecting, whatever) the newly created buffer that
+is passed as argument. Exemplary candidates are `display-buffer',
+`pop-to-buffer' or `switch-to-buffer'.
+
+By default (that is when ACTIVATOR is NIL), the buffer is shown
+and selected by `pop-to-buffer'."
(let ((window-config (current-window-configuration)))
(when (get-buffer name) (kill-buffer name))
(with-current-buffer (get-buffer-create name)
(when mode (funcall mode))
(slime-temp-buffer-mode 1)
(setq slime-temp-buffer-saved-window-configuration window-config)
- (let ((window (if noselectp
- (display-buffer (current-buffer) t)
- (pop-to-buffer (current-buffer))
- (selected-window))))
- (setq slime-temp-buffer-fingerprint (slime-window-config-fingerprint)))
+ (if activator
+ (funcall activator (current-buffer))
+ (pop-to-buffer (current-buffer)))
+ (setq slime-temp-buffer-fingerprint (slime-window-config-fingerprint))
(current-buffer))))
;; Interface
-(defmacro* slime-with-output-to-temp-buffer ((name &optional mode)
+(defmacro* slime-with-output-to-temp-buffer ((name &optional mode activator)
package &rest body)
"Similar to `with-output-to-temp-buffer'.
Also saves the window configuration, and inherits the current
-`slime-connection' in a buffer-local variable."
+`slime-connection' in a buffer-local variable.
+
+MODE and ACTIVATOR are passed to `slime-get-temp-buffer-create',
+see there for an explanation."
`(let ((connection (slime-connection))
- (standard-output (slime-get-temp-buffer-create ,name :mode ',mode)))
+ (standard-output (slime-get-temp-buffer-create ,name :mode ',mode
+ :activator ,activator)))
(prog1 (with-current-buffer standard-output ,@body)
(with-current-buffer standard-output
(setq slime-buffer-connection connection)
@@ -2281,8 +2294,8 @@
This is set only in buffers bound to specific packages."))
;;; `slime-rex' is the RPC primitive which is used to implement both
-;;; `slime-eval' and `slime-eval-async'. You can use it directly you
-;;; need to but the others are usually more convenient.
+;;; `slime-eval' and `slime-eval-async'. You can use it directly if
+;;; you need to, but the others are usually more convenient.
(defmacro* slime-rex ((&rest saved-vars)
(sexp &optional
@@ -4750,7 +4763,8 @@
(defun slime-tree-insert (tree prefix)
"Insert TREE prefixed with PREFIX at point."
- (with-struct (slime-tree. print-fn kids collapsed-p start-mark end-mark) tree
+ (with-struct-slots (print-fn kids collapsed-p start-mark end-mark)
+ (tree :conc-name slime-tree.)
(let ((line-start (line-beginning-position)))
(setf start-mark (point-marker))
(slime-tree-insert-decoration tree)
@@ -4775,7 +4789,8 @@
(defun slime-tree-toggle (tree)
"Toggle the visibility of TREE's children."
- (with-struct (slime-tree. collapsed-p start-mark end-mark prefix) tree
+ (with-struct-slots (collapsed-p start-mark end-mark prefix)
+ (tree :conc-name slime-tree.)
(setf collapsed-p (not collapsed-p))
(slime-tree-delete tree)
(insert-before-markers " ") ; move parent's end-mark
@@ -7301,11 +7316,14 @@
;;;; Macroexpansion
+(defvar slime-macroexpansion-buffer-name "*SLIME macroexpansion*")
+
(define-minor-mode slime-macroexpansion-minor-mode
"SLIME mode for macroexpansion"
nil
" temp"
'(("q" . slime-temp-buffer-quit)
+ ("l" . slime-macroexpansion-buffer-go-back)
("g" . slime-macroexpand-again)))
(flet ((remap (from to)
@@ -7314,44 +7332,172 @@
(remap 'slime-macroexpand-1 'slime-macroexpand-1-inplace)
(remap 'slime-macroexpand-all 'slime-macroexpand-all-inplace))
-(defvar slime-eval-macroexpand-expression nil
- "Specifies the last macroexpansion preformed. This variable
- specifies both what was expanded and how.")
-
-(defun slime-eval-macroexpand (expander &optional string)
- (unless string
- (setf string (slime-sexp-at-point-or-error)))
- (setf slime-eval-macroexpand-expression `(,expander ,string))
- (lexical-let ((package (slime-current-package)))
- (slime-eval-async
- slime-eval-macroexpand-expression
+
+(defvar slime-macroexpansion-stack '()
+ "A stack that consists of SLIME-MACROEXPANSION-STRUCTs which
+ represent the last macroexpansions that have been done in the
+ current SLIME Macroexpansion Buffer")
+
+(defun slime-macroexpansion-kill-buffer-finalizer ()
+ (when (eq (current-buffer) (get-buffer slime-macroexpansion-buffer-name))
+ (setf slime-macroexpansion-stack nil)))
+
+(add-hook 'slime-mode-hook
+ (lambda () (add-hook 'kill-buffer-hook 'slime-macroexpansion-kill-buffer-finalizer)))
+
+(defstruct (slime-macroexpansion-struct (:conc-name slime-macroexpansion-struct.))
+ expander-expression ; s-expression that results in the macroexpansion.
+ expansion ; macroexpansion string returned from SWANK.
+ point ; last cursor position before new in-place expansion,
+ ; for restoring previous context on go-back.
+ highlight-region-start ; start position and end position for
+ highlight-region-end) ; highlight-edits-mode, for restoration as well.
+
+(defun* slime-update-macroexpansion-stack-top (&key expander-expression expansion point
+ highlight-region-start highlight-region-end)
+ "Set the respective struct-slot in the top item of
+SLIME-MACROEXPANSION-STACK to the given value."
+ (when slime-macroexpansion-stack
+ (macrolet ((maybe-setf (source target)
+ `(when ,target (setf ,source ,target))))
+ (with-struct-slots ((stack-top.expander expander-expression)
+ (stack-top.expansion expansion)
+ (stack-top.highlight-start highlight-region-start)
+ (stack-top.highlight-end highlight-region-end)
+ (stack-top.point point))
+ ((first slime-macroexpansion-stack) :conc-name slime-macroexpansion-struct.)
+ (maybe-setf stack-top.expander expander-expression)
+ (maybe-setf stack-top.expansion expansion)
+ (maybe-setf stack-top.point point)
+ (maybe-setf stack-top.highlight-start highlight-region-start)
+ (maybe-setf stack-top.highlight-end highlight-region-end))
+ t)))
+
+(defun* slime-extend-macroexpansion-stack (&rest args)
+ (let ((new-stack-item (apply #'make-slime-macroexpansion-struct args)))
+ (push new-stack-item slime-macroexpansion-stack)))
+
+(defmacro* with-macroexpansion-buffer ((&key reuse save-point buffer activator) &body body)
+ "Find or create the macroexpansion buffer and make it the
+current buffer and prepare it for insertion resp. modification.
+
+Unless BUFFER is explicitely given, a buffer is searched for that
+is named SLIME-MACROEXPANSION-BUFFER-NAME, and, if not found,
+it's created.
+
+ACTIVATOR is a function that takes the macroexpansion buffer
+buffer as argument and activates it. Candidates are, for
+instance, `display-buffer', `pop-to-buffer' or
+`switch-to-buffer'. By default, `pop-to-buffer' is used.
+
+If REUSE is T, keep the content of the macroexpansion buffer,
+otherwise make the buffer empty.
+
+If SAVE-POINT is T, save the current value of point in the top
+item of the SLIME-MACROEXPANSION-STACK before any modification is
+done to the buffer within the scope of this macro.
+"
+ (lexical-let ((macroexpansion-buffer-g (gensym "macroexpansion-buffer-"))
+ (activator-g (gensym "activator-")))
+ ;; Distinguish between the case when the buffer for marcoexpansion
+ ;; exists already and when not; the reason is that
+ ;; SLIME-WITH-OUTPUT-TO-TEMP-BUFFER kills an already existing
+ ;; buffer which would trigger the finalizer in KILL-BUFFER-HOOK
+ ;; (see above.)
+ `(if (or slime-macroexpansion-stack ,buffer)
+ (lexical-let ((,macroexpansion-buffer-g (or ,buffer (get-buffer slime-macroexpansion-buffer-name)))
+ (,activator-g (or ,activator 'pop-to-buffer)))
+ (assert ,macroexpansion-buffer-g) (funcall ,activator-g ,macroexpansion-buffer-g)
+ (with-current-buffer ,macroexpansion-buffer-g
+ (let ((buffer-read-only nil))
+ (when ,save-point (slime-update-macroexpansion-stack-top :point (point)))
+ (unless ,reuse (erase-buffer))
+ ,@body)))
+ (slime-with-output-to-temp-buffer ; create new buffer for macroexpansion.
+ (slime-macroexpansion-buffer-name lisp-mode ,activator) (slime-current-package)
+ (slime-macroexpansion-minor-mode 1)
+ (when ,save-point
+ (slime-update-macroexpansion-stack-top :point (point)))
+ ,@body))))
+
+(defun slime-eval-macroexpand (expander &optional string buffer-activator)
+ (unless string (setf string (slime-sexp-at-point-or-error)))
+ (lexical-let ((expand-expression `(,expander ,string))
+ (activator buffer-activator)) ; necessary for expansion of
+ (slime-eval-async expand-expression ; WITH-MACROEXPANSION-BUFFER
(lambda (expansion)
- (slime-with-output-to-temp-buffer
- ("*SLIME macroexpansion*" lisp-mode) package
- (slime-macroexpansion-minor-mode)
+ (with-macroexpansion-buffer (:reuse nil :save-point t :activator activator)
(insert expansion)
- (font-lock-fontify-buffer))))))
+ (font-lock-fontify-buffer)
+ (goto-char 0)
+ ;; On go-back, highlight the whole buffer because it got
+ ;; overwritten by the new macroexpansion EXPANSION.
+ (slime-update-macroexpansion-stack-top
+ :highlight-region-start (point-min)
+ :highlight-region-end (point-max))
+ (slime-extend-macroexpansion-stack ; FIXME: [*]
+ :expander-expression expand-expression
+ :expansion expansion :point 0
+ :highlight-region-start (point-min)
+ :highlight-region-end (point-max)))))))
+
+;; [*] Room for Improvement, actually: What if nothing new got
+;; expanded, i.e. if EXPANSION is equal to the expansion stored in the
+;; top of the stack -- create a new stack item or not? Currently, it's
+;; done. Of course, we could just compare the two strings, but that
+;; could be tedious and slow. Cool would be if it was possible to get
+;; an EXPANDED-P as additional value from the macroexpand functions in
+;; SWANK.
-(defun slime-eval-macroexpand-inplace (expander)
- "Substitutes the current sexp at place with its macroexpansion.
-NB: Does not affect *slime-eval-macroexpand-expression*"
+(defun slime-eval-macroexpand-inplace (expander)
+ "Substitutes the current sexp at place with its macroexpansion."
(interactive)
(lexical-let* ((string (slime-sexp-at-point-or-error))
- (package (slime-current-package))
(start (point))
(end (+ start (length string)))
- (buffer (current-buffer)))
- (slime-eval-async
- `(,expander ,string)
- (lambda (expansion)
- (with-current-buffer buffer
- (let ((buffer-read-only nil))
- (goto-char start)
- (delete-region start end)
- (insert expansion)
- (goto-char start)
- (indent-sexp)))))))
+ (buffer (current-buffer))
+ (expand-expression `(,expander ,string)))
+ (slime-eval-async expand-expression
+ (lambda (expansion)
+ (with-macroexpansion-buffer (:buffer buffer :reuse t :save-point t)
+ (when slime-use-highlight-edits-mode
+ (slime-remove-edits (point-min) (point-max)))
+ (goto-char start)
+ (delete-region start end)
+ (insert expansion)
+ (goto-char start)
+ (indent-sexp)
+ ;; On go-back, highlight the s-expression that just got
+ ;; expanded away.
+ (slime-update-macroexpansion-stack-top
+ :highlight-region-start start
+ :highlight-region-end end)
+ (slime-extend-macroexpansion-stack
+ :expander-expression expand-expression
+ :expansion (buffer-string) :point start
+ :highlight-region-start start
+ :highlight-region-end (+ start (length expansion))))))))
+
+
+(defun slime-macroexpansion-buffer-go-back ()
+ "Goes back to last macroexpansion by restoring its buffer content,
+point and highlighted edit regions."
+ (interactive)
+ (flet ((last-buffer-in-stack-p (stack) (not (cdr stack))))
+ (if (last-buffer-in-stack-p slime-macroexpansion-stack)
+ (message "Last macroexpansion in stack: Can't go back.")
+ (pop slime-macroexpansion-stack)
+ (with-struct-slots (expansion point highlight-region-start highlight-region-end)
+ ((first slime-macroexpansion-stack) :conc-name slime-macroexpansion-struct.)
+ (with-macroexpansion-buffer (:reuse nil)
+ (insert expansion)
+ (goto-char point)
+ (when slime-use-highlight-edits-mode
+ (slime-remove-edits (point-min) (point-max))
+ (slime-highlight-edits highlight-region-start
+ highlight-region-end)))))))
+
(defun slime-macroexpand-1 (&optional repeatedly)
"Display the macro expansion of the form at point. The form is
@@ -7389,8 +7535,9 @@
(defun slime-macroexpand-again ()
"Reperform the last macroexpansion."
(interactive)
- (slime-eval-macroexpand (first slime-eval-macroexpand-expression)
- (second slime-eval-macroexpand-expression)))
+ (lexical-let* ((stack-top (first slime-macroexpansion-stack))
+ (expr (slime-macroexpansion-struct.expander-expression stack-top)))
+ (slime-eval-macroexpand (first expr) (second expr) 'switch-to-buffer))) ; don't popup new buffer!
;;;; Subprocess control
@@ -9243,8 +9390,8 @@
(slime-unexpected-failures 0)
(slime-expected-failures 0))
(dolist (slime-current-test slime-tests)
- (with-struct (slime-test. name (function fname) inputs)
- slime-current-test
+ (with-struct-slots (name (function fname) inputs)
+ (slime-current-test :conc-name slime-test.)
(slime-test-heading 1 "%s" name)
(dolist (input inputs)
(incf slime-total-tests)