summaryrefslogtreecommitdiff
path: root/vim/bundle/slimv/slime/contrib/swank-presentation-streams.lisp
blob: a83d62ed1b3162affe16779bd3c86518a50bae61 (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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
;;; 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 type identity &optional body)
		(presenting-object object stream
		  (funcall *saved-%print-unreadable-object*
			   object stream type identity body)))))
      (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)