|
;;; swank-presentation-streams.lisp --- Streams that allow attaching object identities
|
|
;;; to portions of output
|
|
;;;
|
|
;;; Authors: Alan Ruttenberg <alanr-l@mumble.net>
|
|
;;; Matthias Koeppe <mkoeppe@mail.math.uni-magdeburg.de>
|
|
;;; Helmut Eller <heller@common-lisp.net>
|
|
;;;
|
|
;;; 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-presentations))
|
|
|
|
;; This file contains a mechanism for printing to the slime repl so
|
|
;; that the printed result remembers what object it is associated
|
|
;; with. This extends the recording of REPL results.
|
|
;;
|
|
;; There are two methods:
|
|
;;
|
|
;; 1. Depends on the ilisp bridge code being installed and ready to
|
|
;; intercept messages in the printed stream. We encode the
|
|
;; information with a message saying that we are starting to print
|
|
;; an object corresponding to a given id and another when we are
|
|
;; done. The process filter notices these and adds the necessary
|
|
;; text properties to the output.
|
|
;;
|
|
;; 2. Use separate protocol messages :presentation-start and
|
|
;; :presentation-end for sending presentations.
|
|
;;
|
|
;; We only do this if we know we are printing to a slime stream,
|
|
;; checked with the method slime-stream-p. Initially this checks for
|
|
;; the knows slime streams looking at *connections*. In cmucl, sbcl, and
|
|
;; openmcl it also checks if it is a pretty-printing stream which
|
|
;; ultimately prints to a slime stream.
|
|
;;
|
|
;; Method 1 seems to be faster, but the printed escape sequences can
|
|
;; disturb the column counting, and thus the layout in pretty-printing.
|
|
;; We use method 1 when a dedicated output stream is used.
|
|
;;
|
|
;; Method 2 is cleaner and works with pretty printing if the pretty
|
|
;; printers support "annotations". We use method 2 when no dedicated
|
|
;; output stream is used.
|
|
|
|
;; Control
|
|
(defvar *enable-presenting-readable-objects* t
|
|
"set this to enable automatically printing presentations for some
|
|
subset of readable objects, such as pathnames." )
|
|
|
|
;; doing it
|
|
|
|
(defmacro presenting-object (object stream &body body)
|
|
"What you use in your code. Wrap this around some printing and that text will
|
|
be sensitive and remember what object it is in the repl"
|
|
`(presenting-object-1 ,object ,stream #'(lambda () ,@body)))
|
|
|
|
(defmacro presenting-object-if (predicate object stream &body body)
|
|
"What you use in your code. Wrap this around some printing and that text will
|
|
be sensitive and remember what object it is in the repl if predicate is true"
|
|
(let ((continue (gensym)))
|
|
`(let ((,continue #'(lambda () ,@body)))
|
|
(if ,predicate
|
|
(presenting-object-1 ,object ,stream ,continue)
|
|
(funcall ,continue)))))
|
|
|
|
;;; Get pretty printer patches for SBCL at load (not compile) time.
|
|
#+#:disable-dangerous-patching ; #+sbcl
|
|
(eval-when (:load-toplevel)
|
|
(handler-bind ((simple-error
|
|
(lambda (c)
|
|
(declare (ignore c))
|
|
(let ((clobber-it (find-restart 'sb-kernel::clobber-it)))
|
|
(when clobber-it (invoke-restart clobber-it))))))
|
|
(sb-ext:without-package-locks
|
|
(swank/sbcl::with-debootstrapping
|
|
(load (make-pathname
|
|
:name "sbcl-pprint-patch"
|
|
:type "lisp"
|
|
:directory (pathname-directory
|
|
swank-loader:*source-directory*)))))))
|
|
|
|
(let ((last-stream nil)
|
|
(last-answer nil))
|
|
(defun slime-stream-p (stream)
|
|
"Check if stream is one of the slime streams, since if it isn't we
|
|
don't want to present anything.
|
|
Two special return values:
|
|
:DEDICATED -- Output ends up on a dedicated output stream
|
|
:REPL-RESULT -- Output ends up on the :repl-results target.
|
|
"
|
|
(if (eq last-stream stream)
|
|
last-answer
|
|
(progn
|
|
(setq last-stream stream)
|
|
(if (eq stream t)
|
|
(setq stream *standard-output*))
|
|
(setq last-answer
|
|
(or #+openmcl
|
|
(and (typep stream 'ccl::xp-stream)
|
|
;(slime-stream-p (ccl::xp-base-stream (slot-value stream 'ccl::xp-structure)))
|
|
(slime-stream-p (ccl::%svref (slot-value stream 'ccl::xp-structure) 1)))
|
|
#+cmu
|
|
(or (and (typep stream 'lisp::indenting-stream)
|
|
(slime-stream-p (lisp::indenting-stream-stream stream)))
|
|
(and (typep stream 'pretty-print::pretty-stream)
|
|
(fboundp 'pretty-print::enqueue-annotation)
|
|
(let ((slime-stream-p
|
|
(slime-stream-p (pretty-print::pretty-stream-target stream))))
|
|
(and ;; Printing through CMUCL pretty
|
|
;; streams is only cleanly
|
|
;; possible if we are using the
|
|
;; bridge-less protocol with
|
|
;; annotations, because the bridge
|
|
;; escape sequences disturb the
|
|
;; pretty printer layout.
|
|
(not (eql slime-stream-p :dedicated-output))
|
|
;; If OK, return the return value
|
|
;; we got from slime-stream-p on
|
|
;; the target stream (could be
|
|
;; :repl-result):
|
|
slime-stream-p))))
|
|
#+sbcl
|
|
(let ()
|
|
(declare (notinline sb-pretty::pretty-stream-target))
|
|
(and (typep stream (find-symbol "PRETTY-STREAM" 'sb-pretty))
|
|
(find-symbol "ENQUEUE-ANNOTATION" 'sb-pretty)
|
|
(not *use-dedicated-output-stream*)
|
|
(slime-stream-p (sb-pretty::pretty-stream-target stream))))
|
|
#+allegro
|
|
(and (typep stream 'excl:xp-simple-stream)
|
|
(slime-stream-p (excl::stream-output-handle stream)))
|
|
(loop for connection in *connections*
|
|
thereis (or (and (eq stream (connection.dedicated-output connection))
|
|
:dedicated)
|
|
(eq stream (connection.socket-io connection))
|
|
(eq stream (connection.user-output connection))
|
|
(eq stream (connection.user-io connection))
|
|
(and (eq stream (connection.repl-results connection))
|
|
:repl-result)))))))))
|
|
|
|
(defun can-present-readable-objects (&optional stream)
|
|
(declare (ignore stream))
|
|
*enable-presenting-readable-objects*)
|
|
|
|
;; If we are printing to an XP (pretty printing) stream, printing the
|
|
;; escape sequences directly would mess up the layout because column
|
|
;; counting is disturbed. Use "annotations" instead.
|
|
#+allegro
|
|
(defun write-annotation (stream function arg)
|
|
(if (typep stream 'excl:xp-simple-stream)
|
|
(excl::schedule-annotation stream function arg)
|
|
(funcall function arg stream nil)))
|
|
#+cmu
|
|
(defun write-annotation (stream function arg)
|
|
(if (and (typep stream 'pp:pretty-stream)
|
|
(fboundp 'pp::enqueue-annotation))
|
|
(pp::enqueue-annotation stream function arg)
|
|
(funcall function arg stream nil)))
|
|
#+sbcl
|
|
(defun write-annotation (stream function arg)
|
|
(let ((enqueue-annotation
|
|
(find-symbol "ENQUEUE-ANNOTATION" 'sb-pretty)))
|
|
(if (and enqueue-annotation
|
|
(typep stream (find-symbol "PRETTY-STREAM" 'sb-pretty)))
|
|
(funcall enqueue-annotation stream function arg)
|
|
(funcall function arg stream nil))))
|
|
#-(or allegro cmu sbcl)
|
|
(defun write-annotation (stream function arg)
|
|
(funcall function arg stream nil))
|
|
|
|
(defstruct presentation-record
|
|
(id)
|
|
(printed-p)
|
|
(target))
|
|
|
|
(defun presentation-start (record stream truncatep)
|
|
(unless truncatep
|
|
;; Don't start new presentations when nothing is going to be
|
|
;; printed due to *print-lines*.
|
|
(let ((pid (presentation-record-id record))
|
|
(target (presentation-record-target record)))
|
|
(case target
|
|
(:dedicated
|
|
;; Use bridge protocol
|
|
(write-string "<" stream)
|
|
(prin1 pid stream)
|
|
(write-string "" stream))
|
|
(t
|
|
(finish-output stream)
|
|
(send-to-emacs `(:presentation-start ,pid ,target)))))
|
|
(setf (presentation-record-printed-p record) t)))
|
|
|
|
(defun presentation-end (record stream truncatep)
|
|
(declare (ignore truncatep))
|
|
;; Always end old presentations that were started.
|
|
(when (presentation-record-printed-p record)
|
|
(let ((pid (presentation-record-id record))
|
|
(target (presentation-record-target record)))
|
|
(case target
|
|
(:dedicated
|
|
;; Use bridge protocol
|
|
(write-string ">" stream)
|
|
(prin1 pid stream)
|
|
(write-string "" stream))
|
|
(t
|
|
(finish-output stream)
|
|
(send-to-emacs `(:presentation-end ,pid ,target)))))))
|
|
|
|
(defun presenting-object-1 (object stream continue)
|
|
"Uses the bridge mechanism with two messages >id and <id. The first one
|
|
says that I am starting to print an object with this id. The second says I am finished"
|
|
;; this declare special is to let the compiler know that *record-repl-results* will eventually be
|
|
;; a global special, even if it isn't when this file is compiled/loaded.
|
|
(declare (special *record-repl-results*))
|
|
(let ((slime-stream-p
|
|
(and *record-repl-results* (slime-stream-p stream))))
|
|
(if slime-stream-p
|
|
(let* ((pid (swank::save-presented-object object))
|
|
(record (make-presentation-record :id pid :printed-p nil
|
|
:target (if (eq slime-stream-p :repl-result)
|
|
:repl-result
|
|
nil))))
|
|
(write-annotation stream #'presentation-start record)
|
|
(multiple-value-prog1
|
|
(funcall continue)
|
|
(write-annotation stream #'presentation-end record)))
|
|
(funcall continue))))
|
|
|
|
(defun present-repl-results-via-presentation-streams (values)
|
|
;; Override a function in swank.lisp, so that
|
|
;; nested presentations work in the REPL result.
|
|
(let ((repl-results (connection.repl-results *emacs-connection*)))
|
|
(flet ((send (value)
|
|
(presenting-object value repl-results
|
|
(prin1 value repl-results))
|
|
(terpri repl-results)))
|
|
(if (null values)
|
|
(progn
|
|
(princ "; No value" repl-results)
|
|
(terpri repl-results))
|
|
(mapc #'send values)))
|
|
(finish-output repl-results)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
#+openmcl
|
|
(in-package :ccl)
|
|
|
|
#+openmcl
|
|
(defun monkey-patch-stream-printing ()
|
|
(let ((*warn-if-redefine-kernel* nil)
|
|
(*warn-if-redefine* nil))
|
|
(defun %print-unreadable-object (object stream type id thunk)
|
|
(cond ((null stream) (setq stream *standard-output*))
|
|
((eq stream t) (setq stream *terminal-io*)))
|
|
(swank::presenting-object object stream
|
|
(write-unreadable-start object stream)
|
|
(when type
|
|
(princ (type-of object) stream)
|
|
(stream-write-char stream #\space))
|
|
(when thunk
|
|
(funcall thunk))
|
|
(if id
|
|
(%write-address object stream #\>)
|
|
(pp-end-block stream ">"))
|
|
nil))
|
|
(defmethod print-object :around ((pathname pathname) stream)
|
|
(swank::presenting-object-if
|
|
(swank::can-present-readable-objects stream)
|
|
pathname stream (call-next-method))))
|
|
(ccl::def-load-pointers clear-presentations ()
|
|
(swank::clear-presentation-tables)))
|
|
|
|
(in-package :swank)
|
|
|
|
#+cmu
|
|
(progn
|
|
(fwrappers:define-fwrapper presenting-unreadable-wrapper (object stream type identity body)
|
|
(presenting-object object stream
|
|
(fwrappers:call-next-function)))
|
|
|
|
(fwrappers:define-fwrapper presenting-pathname-wrapper (pathname stream depth)
|
|
(presenting-object-if (can-present-readable-objects stream) pathname stream
|
|
(fwrappers:call-next-function)))
|
|
|
|
(defun monkey-patch-stream-printing ()
|
|
(fwrappers::fwrap 'lisp::%print-pathname #'presenting-pathname-wrapper)
|
|
(fwrappers::fwrap 'lisp::%print-unreadable-object #'presenting-unreadable-wrapper)))
|
|
|
|
#+sbcl
|
|
(progn
|
|
(defvar *saved-%print-unreadable-object*
|
|
(fdefinition 'sb-impl::%print-unreadable-object))
|
|
|
|
(defun monkey-patch-stream-printing ()
|
|
(sb-ext:without-package-locks
|
|
(when (eq (fdefinition 'sb-impl::%print-unreadable-object)
|
|
*saved-%print-unreadable-object*)
|
|
(setf (fdefinition 'sb-impl::%print-unreadable-object)
|
|
(lambda (object stream &rest args)
|
|
(presenting-object object stream
|
|
(apply *saved-%print-unreadable-object*
|
|
object stream args)))))
|
|
(defmethod print-object :around ((object pathname) stream)
|
|
(presenting-object object stream
|
|
(call-next-method))))))
|
|
|
|
#+allegro
|
|
(progn
|
|
(excl:def-fwrapper presenting-unreadable-wrapper (object stream type identity continuation)
|
|
(swank::presenting-object object stream (excl:call-next-fwrapper)))
|
|
(excl:def-fwrapper presenting-pathname-wrapper (pathname stream depth)
|
|
(presenting-object-if (can-present-readable-objects stream) pathname stream
|
|
(excl:call-next-fwrapper)))
|
|
(defun monkey-patch-stream-printing ()
|
|
(excl:fwrap 'excl::print-unreadable-object-1
|
|
'print-unreadable-present 'presenting-unreadable-wrapper)
|
|
(excl:fwrap 'excl::pathname-printer
|
|
'print-pathname-present 'presenting-pathname-wrapper)))
|
|
|
|
#-(or allegro sbcl cmu openmcl)
|
|
(defun monkey-patch-stream-printing ()
|
|
(values))
|
|
|
|
;; Hook into SWANK.
|
|
|
|
(defslimefun init-presentation-streams ()
|
|
(monkey-patch-stream-printing)
|
|
;; FIXME: import/use swank-repl to avoid package qualifier.
|
|
(setq swank-repl:*send-repl-results-function*
|
|
'present-repl-results-via-presentation-streams))
|
|
|
|
(provide :swank-presentation-streams)
|