;;; swank-presentations.lisp --- imitate LispM's presentations
|
|
;;
|
|
;; Authors: Alan Ruttenberg <alanr-l@mumble.net>
|
|
;; Luke Gorrie <luke@synap.se>
|
|
;; Helmut Eller <heller@common-lisp.net>
|
|
;; Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de>
|
|
;;
|
|
;; License: This code has been placed in the Public Domain. All warranties
|
|
;; are disclaimed.
|
|
;;
|
|
|
|
(in-package :swank)
|
|
|
|
(eval-when (:compile-toplevel :load-toplevel :execute)
|
|
(swank-require :swank-repl))
|
|
|
|
;;;; Recording and accessing results of computations
|
|
|
|
(defvar *record-repl-results* t
|
|
"Non-nil means that REPL results are saved for later lookup.")
|
|
|
|
(defvar *object-to-presentation-id*
|
|
(make-weak-key-hash-table :test 'eq)
|
|
"Store the mapping of objects to numeric identifiers")
|
|
|
|
(defvar *presentation-id-to-object*
|
|
(make-weak-value-hash-table :test 'eql)
|
|
"Store the mapping of numeric identifiers to objects")
|
|
|
|
(defun clear-presentation-tables ()
|
|
(clrhash *object-to-presentation-id*)
|
|
(clrhash *presentation-id-to-object*))
|
|
|
|
(defvar *presentation-counter* 0 "identifier counter")
|
|
|
|
(defvar *nil-surrogate* (make-symbol "nil-surrogate"))
|
|
|
|
;; XXX thread safety? [2006-09-13] mb: not in the slightest (fwiw the
|
|
;; rest of slime isn't thread safe either), do we really care?
|
|
(defun save-presented-object (object)
|
|
"Save OBJECT and return the assigned id.
|
|
If OBJECT was saved previously return the old id."
|
|
(let ((object (if (null object) *nil-surrogate* object)))
|
|
;; We store *nil-surrogate* instead of nil, to distinguish it from
|
|
;; an object that was garbage collected.
|
|
(or (gethash object *object-to-presentation-id*)
|
|
(let ((id (incf *presentation-counter*)))
|
|
(setf (gethash id *presentation-id-to-object*) object)
|
|
(setf (gethash object *object-to-presentation-id*) id)
|
|
id))))
|
|
|
|
(defslimefun lookup-presented-object (id)
|
|
"Retrieve the object corresponding to ID.
|
|
The secondary value indicates the absence of an entry."
|
|
(etypecase id
|
|
(integer
|
|
;;
|
|
(multiple-value-bind (object foundp)
|
|
(gethash id *presentation-id-to-object*)
|
|
(cond
|
|
((eql object *nil-surrogate*)
|
|
;; A stored nil object
|
|
(values nil t))
|
|
((null object)
|
|
;; Object that was replaced by nil in the weak hash table
|
|
;; when the object was garbage collected.
|
|
(values nil nil))
|
|
(t
|
|
(values object foundp)))))
|
|
(cons
|
|
(dcase id
|
|
((:frame-var thread-id frame index)
|
|
(declare (ignore thread-id)) ; later
|
|
(handler-case
|
|
(frame-var-value frame index)
|
|
(t (condition)
|
|
(declare (ignore condition))
|
|
(values nil nil))
|
|
(:no-error (value)
|
|
(values value t))))
|
|
((:inspected-part part-index)
|
|
(inspector-nth-part part-index))))))
|
|
|
|
(defslimefun lookup-presented-object-or-lose (id)
|
|
"Get the result of the previous REPL evaluation with ID."
|
|
(multiple-value-bind (object foundp) (lookup-presented-object id)
|
|
(cond (foundp object)
|
|
(t (error "Attempt to access unrecorded object (id ~D)." id)))))
|
|
|
|
(defslimefun lookup-and-save-presented-object-or-lose (id)
|
|
"Get the object associated with ID and save it in the presentation tables."
|
|
(let ((obj (lookup-presented-object-or-lose id)))
|
|
(save-presented-object obj)))
|
|
|
|
(defslimefun clear-repl-results ()
|
|
"Forget the results of all previous REPL evaluations."
|
|
(clear-presentation-tables)
|
|
t)
|
|
|
|
(defun present-repl-results (values)
|
|
;; Override a function in swank.lisp, so that
|
|
;; presentations are associated with every REPL result.
|
|
(flet ((send (value)
|
|
(let ((id (and *record-repl-results*
|
|
(save-presented-object value))))
|
|
(send-to-emacs `(:presentation-start ,id :repl-result))
|
|
(send-to-emacs `(:write-string ,(prin1-to-string value)
|
|
:repl-result))
|
|
(send-to-emacs `(:presentation-end ,id :repl-result))
|
|
(send-to-emacs `(:write-string ,(string #\Newline)
|
|
:repl-result)))))
|
|
(fresh-line)
|
|
(finish-output)
|
|
(if (null values)
|
|
(send-to-emacs `(:write-string "; No value" :repl-result))
|
|
(mapc #'send values))))
|
|
|
|
|
|
;;;; Presentation menu protocol
|
|
;;
|
|
;; To define a menu for a type of object, define a method
|
|
;; menu-choices-for-presentation on that object type. This function
|
|
;; should return a list of two element lists where the first element is
|
|
;; the name of the menu action and the second is a function that will be
|
|
;; called if the menu is chosen. The function will be called with 3
|
|
;; arguments:
|
|
;;
|
|
;; choice: The string naming the action from above
|
|
;;
|
|
;; object: The object
|
|
;;
|
|
;; id: The presentation id of the object
|
|
;;
|
|
;; You might want append (when (next-method-p) (call-next-method)) to
|
|
;; pick up the Menu actions of superclasses.
|
|
;;
|
|
|
|
(defvar *presentation-active-menu* nil)
|
|
|
|
(defun menu-choices-for-presentation-id (id)
|
|
(multiple-value-bind (ob presentp) (lookup-presented-object id)
|
|
(cond ((not presentp) 'not-present)
|
|
(t
|
|
(let ((menu-and-actions (menu-choices-for-presentation ob)))
|
|
(setq *presentation-active-menu* (cons id menu-and-actions))
|
|
(mapcar 'car menu-and-actions))))))
|
|
|
|
(defun swank-ioify (thing)
|
|
(cond ((keywordp thing) thing)
|
|
((and (symbolp thing)(not (find #\: (symbol-name thing))))
|
|
(intern (symbol-name thing) 'swank-io-package))
|
|
((consp thing) (cons (swank-ioify (car thing))
|
|
(swank-ioify (cdr thing))))
|
|
(t thing)))
|
|
|
|
(defun execute-menu-choice-for-presentation-id (id count item)
|
|
(let ((ob (lookup-presented-object id)))
|
|
(assert (equal id (car *presentation-active-menu*)) ()
|
|
"Bug: Execute menu call for id ~a but menu has id ~a"
|
|
id (car *presentation-active-menu*))
|
|
(let ((action (second (nth (1- count) (cdr *presentation-active-menu*)))))
|
|
(swank-ioify (funcall action item ob id)))))
|
|
|
|
|
|
(defgeneric menu-choices-for-presentation (object)
|
|
(:method (ob) (declare (ignore ob)) nil)) ; default method
|
|
|
|
;; Pathname
|
|
(defmethod menu-choices-for-presentation ((ob pathname))
|
|
(let* ((file-exists (ignore-errors (probe-file ob)))
|
|
(lisp-type (make-pathname :type "lisp"))
|
|
(source-file (and (not (member (pathname-type ob) '("lisp" "cl")
|
|
:test 'equal))
|
|
(let ((source (merge-pathnames lisp-type ob)))
|
|
(and (ignore-errors (probe-file source))
|
|
source))))
|
|
(fasl-file (and file-exists
|
|
(equal (ignore-errors
|
|
(namestring
|
|
(truename
|
|
(compile-file-pathname
|
|
(merge-pathnames lisp-type ob)))))
|
|
(namestring (truename ob))))))
|
|
(remove nil
|
|
(list*
|
|
(and (and file-exists (not fasl-file))
|
|
(list "Edit this file"
|
|
(lambda(choice object id)
|
|
(declare (ignore choice id))
|
|
(ed-in-emacs (namestring (truename object)))
|
|
nil)))
|
|
(and file-exists
|
|
(list "Dired containing directory"
|
|
(lambda (choice object id)
|
|
(declare (ignore choice id))
|
|
(ed-in-emacs (namestring
|
|
(truename
|
|
(merge-pathnames
|
|
(make-pathname :name "" :type "")
|
|
object))))
|
|
nil)))
|
|
(and fasl-file
|
|
(list "Load this fasl file"
|
|
(lambda (choice object id)
|
|
(declare (ignore choice id object))
|
|
(load ob)
|
|
nil)))
|
|
(and fasl-file
|
|
(list "Delete this fasl file"
|
|
(lambda (choice object id)
|
|
(declare (ignore choice id object))
|
|
(let ((nt (namestring (truename ob))))
|
|
(when (y-or-n-p-in-emacs "Delete ~a? " nt)
|
|
(delete-file nt)))
|
|
nil)))
|
|
(and source-file
|
|
(list "Edit lisp source file"
|
|
(lambda (choice object id)
|
|
(declare (ignore choice id object))
|
|
(ed-in-emacs (namestring (truename source-file)))
|
|
nil)))
|
|
(and source-file
|
|
(list "Load lisp source file"
|
|
(lambda(choice object id)
|
|
(declare (ignore choice id object))
|
|
(load source-file)
|
|
nil)))
|
|
(and (next-method-p) (call-next-method))))))
|
|
|
|
(defmethod menu-choices-for-presentation ((ob function))
|
|
(list (list "Disassemble"
|
|
(lambda (choice object id)
|
|
(declare (ignore choice id))
|
|
(disassemble object)))))
|
|
|
|
(defslimefun inspect-presentation (id reset-p)
|
|
(let ((what (lookup-presented-object-or-lose id)))
|
|
(when reset-p
|
|
(reset-inspector))
|
|
(inspect-object what)))
|
|
|
|
(defslimefun init-presentations ()
|
|
;; FIXME: import/use swank-repl to avoid package qualifier.
|
|
(setq swank-repl:*send-repl-results-function* 'present-repl-results))
|
|
|
|
(provide :swank-presentations)
|