Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
marvel
GitHub Repository: marvel/qnf
Path: blob/master/elisp/slime/contrib/swank-fancy-inspector.lisp
990 views
1
;;; swank-fancy-inspector.lisp --- Fancy inspector for CLOS objects
2
;;
3
;; Author: Marco Baringer <[email protected]> and others
4
;; License: Public Domain
5
;;
6
7
(in-package :swank)
8
9
(defmethod emacs-inspect ((symbol symbol))
10
(let ((package (symbol-package symbol)))
11
(multiple-value-bind (_symbol status)
12
(and package (find-symbol (string symbol) package))
13
(declare (ignore _symbol))
14
(append
15
(label-value-line "Its name is" (symbol-name symbol))
16
;;
17
;; Value
18
(cond ((boundp symbol)
19
(append
20
(label-value-line (if (constantp symbol)
21
"It is a constant of value"
22
"It is a global variable bound to")
23
(symbol-value symbol) :newline nil)
24
;; unbinding constants might be not a good idea, but
25
;; implementations usually provide a restart.
26
`(" " (:action "[unbind it]"
27
,(lambda () (makunbound symbol))))
28
'((:newline))))
29
(t '("It is unbound." (:newline))))
30
(docstring-ispec "Documentation" symbol 'variable)
31
(multiple-value-bind (expansion definedp) (macroexpand symbol)
32
(if definedp
33
(label-value-line "It is a symbol macro with expansion"
34
expansion)))
35
;;
36
;; Function
37
(if (fboundp symbol)
38
(append (if (macro-function symbol)
39
`("It a macro with macro-function: "
40
(:value ,(macro-function symbol)))
41
`("It is a function: "
42
(:value ,(symbol-function symbol))))
43
`(" " (:action "[unbind it]"
44
,(lambda () (fmakunbound symbol))))
45
`((:newline)))
46
`("It has no function value." (:newline)))
47
(docstring-ispec "Function Documentation" symbol 'function)
48
(when (compiler-macro-function symbol)
49
50
(append
51
(label-value-line "It also names the compiler macro"
52
(compiler-macro-function symbol) :newline nil)
53
`(" " (:action "[remove it]"
54
,(lambda ()
55
(setf (compiler-macro-function symbol) nil)))
56
(:newline))))
57
(docstring-ispec "Compiler Macro Documentation"
58
symbol 'compiler-macro)
59
;;
60
;; Package
61
(if package
62
`("It is " ,(string-downcase (string status))
63
" to the package: "
64
(:value ,package ,(package-name package))
65
,@(if (eq :internal status)
66
`(" "
67
(:action "[export it]"
68
,(lambda () (export symbol package)))))
69
" "
70
(:action "[unintern it]"
71
,(lambda () (unintern symbol package)))
72
(:newline))
73
'("It is a non-interned symbol." (:newline)))
74
;;
75
;; Plist
76
(label-value-line "Property list" (symbol-plist symbol))
77
;;
78
;; Class
79
(if (find-class symbol nil)
80
`("It names the class "
81
(:value ,(find-class symbol) ,(string symbol))
82
" "
83
(:action "[remove]"
84
,(lambda () (setf (find-class symbol) nil)))
85
(:newline)))
86
;;
87
;; More package
88
(if (find-package symbol)
89
(label-value-line "It names the package" (find-package symbol)))
90
))))
91
92
(defun docstring-ispec (label object kind)
93
"Return a inspector spec if OBJECT has a docstring of of kind KIND."
94
(let ((docstring (documentation object kind)))
95
(cond ((not docstring) nil)
96
((< (+ (length label) (length docstring))
97
75)
98
(list label ": " docstring '(:newline)))
99
(t
100
(list label ": " '(:newline) " " docstring '(:newline))))))
101
102
(unless (find-method #'emacs-inspect '() (list (find-class 'function)) nil)
103
(defmethod emacs-inspect ((f function))
104
(inspect-function f)))
105
106
(defun inspect-function (f)
107
(append
108
(label-value-line "Name" (function-name f))
109
`("Its argument list is: "
110
,(inspector-princ (arglist f)) (:newline))
111
(docstring-ispec "Documentation" f t)
112
(if (function-lambda-expression f)
113
(label-value-line "Lambda Expression"
114
(function-lambda-expression f)))))
115
116
(defun method-specializers-for-inspect (method)
117
"Return a \"pretty\" list of the method's specializers. Normal
118
specializers are replaced by the name of the class, eql
119
specializers are replaced by `(eql ,object)."
120
(mapcar (lambda (spec)
121
(typecase spec
122
(swank-mop:eql-specializer
123
`(eql ,(swank-mop:eql-specializer-object spec)))
124
(t (swank-mop:class-name spec))))
125
(swank-mop:method-specializers method)))
126
127
(defun method-for-inspect-value (method)
128
"Returns a \"pretty\" list describing METHOD. The first element
129
of the list is the name of generic-function method is
130
specialiazed on, the second element is the method qualifiers,
131
the rest of the list is the method's specialiazers (as per
132
method-specializers-for-inspect)."
133
(append (list (swank-mop:generic-function-name
134
(swank-mop:method-generic-function method)))
135
(swank-mop:method-qualifiers method)
136
(method-specializers-for-inspect method)))
137
138
(defmethod emacs-inspect ((object standard-object))
139
(let ((class (class-of object)))
140
`("Class: " (:value ,class) (:newline)
141
,@(all-slots-for-inspector object))))
142
143
(defvar *gf-method-getter* 'methods-by-applicability
144
"This function is called to get the methods of a generic function.
145
The default returns the method sorted by applicability.
146
See `methods-by-applicability'.")
147
148
(defun specializer< (specializer1 specializer2)
149
"Return true if SPECIALIZER1 is more specific than SPECIALIZER2."
150
(let ((s1 specializer1) (s2 specializer2) )
151
(cond ((typep s1 'swank-mop:eql-specializer)
152
(not (typep s2 'swank-mop:eql-specializer)))
153
(t
154
(flet ((cpl (class)
155
(and (swank-mop:class-finalized-p class)
156
(swank-mop:class-precedence-list class))))
157
(member s2 (cpl s1)))))))
158
159
(defun methods-by-applicability (gf)
160
"Return methods ordered by most specific argument types.
161
162
`method-specializer<' is used for sorting."
163
;; FIXME: argument-precedence-order and qualifiers are ignored.
164
(labels ((method< (meth1 meth2)
165
(loop for s1 in (swank-mop:method-specializers meth1)
166
for s2 in (swank-mop:method-specializers meth2)
167
do (cond ((specializer< s2 s1) (return nil))
168
((specializer< s1 s2) (return t))))))
169
(stable-sort (copy-seq (swank-mop:generic-function-methods gf)) #'method<)))
170
171
(defun abbrev-doc (doc &optional (maxlen 80))
172
"Return the first sentence of DOC, but not more than MAXLAN characters."
173
(subseq doc 0 (min (1+ (or (position #\. doc) (1- maxlen)))
174
maxlen
175
(length doc))))
176
177
(defstruct (inspector-checklist (:conc-name checklist.)
178
(:constructor %make-checklist (buttons)))
179
(buttons nil :type (or null simple-vector))
180
(count 0))
181
182
(defun make-checklist (n)
183
(%make-checklist (make-array n :initial-element nil)))
184
185
(defun reinitialize-checklist (checklist)
186
;; Along this counter the buttons are created, so we have to
187
;; initialize it to 0 everytime the inspector page is redisplayed.
188
(setf (checklist.count checklist) 0)
189
checklist)
190
191
(defun make-checklist-button (checklist)
192
(let ((buttons (checklist.buttons checklist))
193
(i (checklist.count checklist)))
194
(incf (checklist.count checklist))
195
`(:action ,(if (svref buttons i)
196
"[X]"
197
"[ ]")
198
,#'(lambda ()
199
(setf (svref buttons i) (not (svref buttons i))))
200
:refreshp t)))
201
202
(defmacro do-checklist ((idx checklist) &body body)
203
"Iterate over all set buttons in CHECKLIST."
204
(let ((buttons (gensym "buttons")))
205
`(let ((,buttons (checklist.buttons ,checklist)))
206
(dotimes (,idx (length ,buttons))
207
(when (svref ,buttons ,idx)
208
,@body)))))
209
210
(defun box (thing) (cons :box thing))
211
(defun ref (box)
212
(assert (eq (car box) :box))
213
(cdr box))
214
(defun (setf ref) (value box)
215
(assert (eq (car box) :box))
216
(setf (cdr box) value))
217
218
(defvar *inspector-slots-default-order* :alphabetically
219
"Accepted values: :alphabetically and :unsorted")
220
221
(defvar *inspector-slots-default-grouping* :all
222
"Accepted values: :inheritance and :all")
223
224
(defgeneric all-slots-for-inspector (object))
225
226
(defmethod all-slots-for-inspector ((object standard-object))
227
(let* ((class (class-of object))
228
(direct-slots (swank-mop:class-direct-slots class))
229
(effective-slots (swank-mop:class-slots class))
230
(longest-slot-name-length
231
(loop for slot :in effective-slots
232
maximize (length (symbol-name
233
(swank-mop:slot-definition-name slot)))))
234
(checklist
235
(reinitialize-checklist
236
(ensure-istate-metadata object :checklist
237
(make-checklist (length effective-slots)))))
238
(grouping-kind
239
;; We box the value so we can re-set it.
240
(ensure-istate-metadata object :grouping-kind
241
(box *inspector-slots-default-grouping*)))
242
(sort-order
243
(ensure-istate-metadata object :sort-order
244
(box *inspector-slots-default-order*)))
245
(sort-predicate (ecase (ref sort-order)
246
(:alphabetically #'string<)
247
(:unsorted (constantly nil))))
248
(sorted-slots (sort (copy-seq effective-slots)
249
sort-predicate
250
:key #'swank-mop:slot-definition-name))
251
(effective-slots
252
(ecase (ref grouping-kind)
253
(:all sorted-slots)
254
(:inheritance (stable-sort-by-inheritance sorted-slots class sort-predicate)))))
255
`("--------------------"
256
(:newline)
257
" Group slots by inheritance "
258
(:action ,(ecase (ref grouping-kind)
259
(:all "[ ]")
260
(:inheritance "[X]"))
261
,(lambda ()
262
;; We have to do this as the order of slots will
263
;; be sorted differently.
264
(fill (checklist.buttons checklist) nil)
265
(setf (ref grouping-kind)
266
(ecase (ref grouping-kind)
267
(:all :inheritance)
268
(:inheritance :all))))
269
:refreshp t)
270
(:newline)
271
" Sort slots alphabetically "
272
(:action ,(ecase (ref sort-order)
273
(:unsorted "[ ]")
274
(:alphabetically "[X]"))
275
,(lambda ()
276
(fill (checklist.buttons checklist) nil)
277
(setf (ref sort-order)
278
(ecase (ref sort-order)
279
(:unsorted :alphabetically)
280
(:alphabetically :unsorted))))
281
:refreshp t)
282
(:newline)
283
,@ (case (ref grouping-kind)
284
(:all
285
`((:newline)
286
"All Slots:"
287
(:newline)
288
,@(make-slot-listing checklist object class
289
effective-slots direct-slots
290
longest-slot-name-length)))
291
(:inheritance
292
(list-all-slots-by-inheritance checklist object class
293
effective-slots direct-slots
294
longest-slot-name-length)))
295
(:newline)
296
(:action "[set value]"
297
,(lambda ()
298
(do-checklist (idx checklist)
299
(query-and-set-slot class object
300
(nth idx effective-slots))))
301
:refreshp t)
302
" "
303
(:action "[make unbound]"
304
,(lambda ()
305
(do-checklist (idx checklist)
306
(swank-mop:slot-makunbound-using-class
307
class object (nth idx effective-slots))))
308
:refreshp t)
309
(:newline))))
310
311
(defun list-all-slots-by-inheritance (checklist object class effective-slots
312
direct-slots longest-slot-name-length)
313
(flet ((slot-home-class (slot)
314
(slot-home-class-using-class slot class)))
315
(let ((current-slots '()))
316
(append
317
(loop for slot in effective-slots
318
for previous-home-class = (slot-home-class slot) then home-class
319
for home-class = previous-home-class then (slot-home-class slot)
320
if (eq home-class previous-home-class)
321
do (push slot current-slots)
322
else
323
collect '(:newline)
324
and collect (format nil "~A:" (class-name previous-home-class))
325
and collect '(:newline)
326
and append (make-slot-listing checklist object class
327
(nreverse current-slots) direct-slots
328
longest-slot-name-length)
329
and do (setf current-slots (list slot)))
330
(and current-slots
331
`((:newline)
332
,(format nil "~A:"
333
(class-name (slot-home-class-using-class
334
(car current-slots) class)))
335
(:newline)
336
,@(make-slot-listing checklist object class
337
(nreverse current-slots) direct-slots
338
longest-slot-name-length)))))))
339
340
(defun make-slot-listing (checklist object class effective-slots direct-slots
341
longest-slot-name-length)
342
(flet ((padding-for (slot-name)
343
(make-string (- longest-slot-name-length (length slot-name))
344
:initial-element #\Space)))
345
(loop
346
for effective-slot :in effective-slots
347
for direct-slot = (find (swank-mop:slot-definition-name effective-slot)
348
direct-slots :key #'swank-mop:slot-definition-name)
349
for slot-name = (inspector-princ
350
(swank-mop:slot-definition-name effective-slot))
351
collect (make-checklist-button checklist)
352
collect " "
353
collect `(:value ,(if direct-slot
354
(list direct-slot effective-slot)
355
effective-slot)
356
,slot-name)
357
collect (padding-for slot-name)
358
collect " = "
359
collect (slot-value-for-inspector class object effective-slot)
360
collect '(:newline))))
361
362
(defgeneric slot-value-for-inspector (class object slot)
363
(:method (class object slot)
364
(let ((boundp (swank-mop:slot-boundp-using-class class object slot)))
365
(if boundp
366
`(:value ,(swank-mop:slot-value-using-class class object slot))
367
"#<unbound>"))))
368
369
(defun slot-home-class-using-class (slot class)
370
(let ((slot-name (swank-mop:slot-definition-name slot)))
371
(loop for class in (reverse (swank-mop:class-precedence-list class))
372
thereis (and (member slot-name (swank-mop:class-direct-slots class)
373
:key #'swank-mop:slot-definition-name :test #'eq)
374
class))))
375
376
(defun stable-sort-by-inheritance (slots class predicate)
377
(stable-sort slots predicate
378
:key #'(lambda (s)
379
(class-name (slot-home-class-using-class s class)))))
380
381
(defun query-and-set-slot (class object slot)
382
(let* ((slot-name (swank-mop:slot-definition-name slot))
383
(value-string (read-from-minibuffer-in-emacs
384
(format nil "Set slot ~S to (evaluated) : "
385
slot-name))))
386
(when (and value-string (not (string= value-string "")))
387
(with-simple-restart (abort "Abort setting slot ~S" slot-name)
388
(setf (swank-mop:slot-value-using-class class object slot)
389
(eval (read-from-string value-string)))))))
390
391
392
(defmethod emacs-inspect ((gf standard-generic-function))
393
(flet ((lv (label value) (label-value-line label value)))
394
(append
395
(lv "Name" (swank-mop:generic-function-name gf))
396
(lv "Arguments" (swank-mop:generic-function-lambda-list gf))
397
(docstring-ispec "Documentation" gf t)
398
(lv "Method class" (swank-mop:generic-function-method-class gf))
399
(lv "Method combination"
400
(swank-mop:generic-function-method-combination gf))
401
`("Methods: " (:newline))
402
(loop for method in (funcall *gf-method-getter* gf) append
403
`((:value ,method ,(inspector-princ
404
;; drop the name of the GF
405
(cdr (method-for-inspect-value method))))
406
" "
407
(:action "[remove method]"
408
,(let ((m method)) ; LOOP reassigns method
409
(lambda ()
410
(remove-method gf m))))
411
(:newline)))
412
`((:newline))
413
(all-slots-for-inspector gf))))
414
415
(defmethod emacs-inspect ((method standard-method))
416
`("Method defined on the generic function "
417
(:value ,(swank-mop:method-generic-function method)
418
,(inspector-princ
419
(swank-mop:generic-function-name
420
(swank-mop:method-generic-function method))))
421
(:newline)
422
,@(docstring-ispec "Documentation" method t)
423
"Lambda List: " (:value ,(swank-mop:method-lambda-list method))
424
(:newline)
425
"Specializers: " (:value ,(swank-mop:method-specializers method)
426
,(inspector-princ (method-specializers-for-inspect method)))
427
(:newline)
428
"Qualifiers: " (:value ,(swank-mop:method-qualifiers method))
429
(:newline)
430
"Method function: " (:value ,(swank-mop:method-function method))
431
(:newline)
432
,@(all-slots-for-inspector method)))
433
434
(defmethod emacs-inspect ((class standard-class))
435
`("Name: " (:value ,(class-name class))
436
(:newline)
437
"Super classes: "
438
,@(common-seperated-spec (swank-mop:class-direct-superclasses class))
439
(:newline)
440
"Direct Slots: "
441
,@(common-seperated-spec
442
(swank-mop:class-direct-slots class)
443
(lambda (slot)
444
`(:value ,slot ,(inspector-princ (swank-mop:slot-definition-name slot)))))
445
(:newline)
446
"Effective Slots: "
447
,@(if (swank-mop:class-finalized-p class)
448
(common-seperated-spec
449
(swank-mop:class-slots class)
450
(lambda (slot)
451
`(:value ,slot ,(inspector-princ
452
(swank-mop:slot-definition-name slot)))))
453
`("#<N/A (class not finalized)> "
454
(:action "[finalize]"
455
,(lambda () (swank-mop:finalize-inheritance class)))))
456
(:newline)
457
,@(let ((doc (documentation class t)))
458
(when doc
459
`("Documentation:" (:newline) ,(inspector-princ doc) (:newline))))
460
"Sub classes: "
461
,@(common-seperated-spec (swank-mop:class-direct-subclasses class)
462
(lambda (sub)
463
`(:value ,sub ,(inspector-princ (class-name sub)))))
464
(:newline)
465
"Precedence List: "
466
,@(if (swank-mop:class-finalized-p class)
467
(common-seperated-spec (swank-mop:class-precedence-list class)
468
(lambda (class)
469
`(:value ,class ,(inspector-princ (class-name class)))))
470
'("#<N/A (class not finalized)>"))
471
(:newline)
472
,@(when (swank-mop:specializer-direct-methods class)
473
`("It is used as a direct specializer in the following methods:" (:newline)
474
,@(loop
475
for method in (sort (copy-seq (swank-mop:specializer-direct-methods class))
476
#'string< :key (lambda (x)
477
(symbol-name
478
(let ((name (swank-mop::generic-function-name
479
(swank-mop::method-generic-function x))))
480
(if (symbolp name) name (second name))))))
481
collect " "
482
collect `(:value ,method ,(inspector-princ (method-for-inspect-value method)))
483
collect '(:newline)
484
if (documentation method t)
485
collect " Documentation: " and
486
collect (abbrev-doc (documentation method t)) and
487
collect '(:newline))))
488
"Prototype: " ,(if (swank-mop:class-finalized-p class)
489
`(:value ,(swank-mop:class-prototype class))
490
'"#<N/A (class not finalized)>")
491
(:newline)
492
,@(all-slots-for-inspector class)))
493
494
(defmethod emacs-inspect ((slot swank-mop:standard-slot-definition))
495
`("Name: " (:value ,(swank-mop:slot-definition-name slot))
496
(:newline)
497
,@(when (swank-mop:slot-definition-documentation slot)
498
`("Documentation:" (:newline)
499
(:value ,(swank-mop:slot-definition-documentation slot))
500
(:newline)))
501
"Init args: " (:value ,(swank-mop:slot-definition-initargs slot)) (:newline)
502
"Init form: " ,(if (swank-mop:slot-definition-initfunction slot)
503
`(:value ,(swank-mop:slot-definition-initform slot))
504
"#<unspecified>") (:newline)
505
"Init function: " (:value ,(swank-mop:slot-definition-initfunction slot))
506
(:newline)
507
,@(all-slots-for-inspector slot)))
508
509
510
;; Wrapper structure over the list of symbols of a package that should
511
;; be displayed with their respective classification flags. This is
512
;; because we need a unique type to dispatch on in EMACS-INSPECT.
513
;; Used by the Inspector for packages.
514
(defstruct (%package-symbols-container (:conc-name %container.)
515
(:constructor %%make-package-symbols-container))
516
title ;; A string; the title of the inspector page in Emacs.
517
description ;; A list of renderable objects; used as description.
518
symbols ;; A list of symbols. Supposed to be sorted alphabetically.
519
grouping-kind ;; Either :SYMBOL or :CLASSIFICATION. Cf. MAKE-SYMBOLS-LISTING.
520
)
521
522
(defun %make-package-symbols-container (&key title description symbols)
523
(%%make-package-symbols-container :title title :description description
524
:symbols symbols :grouping-kind :symbol))
525
526
(defgeneric make-symbols-listing (grouping-kind symbols))
527
528
(defmethod make-symbols-listing ((grouping-kind (eql :symbol)) symbols)
529
"Returns an object renderable by Emacs' inspector side that
530
alphabetically lists all the symbols in SYMBOLS together with a
531
concise string representation of what each symbol
532
represents (see SYMBOL-CLASSIFICATION-STRING)"
533
(let ((max-length (loop for s in symbols maximizing (length (symbol-name s))))
534
(distance 10)) ; empty distance between name and classification
535
(flet ((string-representations (symbol)
536
(let* ((name (symbol-name symbol))
537
(length (length name))
538
(padding (- max-length length)))
539
(values
540
(concatenate 'string
541
name
542
(make-string (+ padding distance) :initial-element #\Space))
543
(symbol-classification-string symbol)))))
544
`("" ; 8 is (length "Symbols:")
545
"Symbols:" ,(make-string (+ -8 max-length distance) :initial-element #\Space) "Flags:"
546
(:newline)
547
,(concatenate 'string ; underlining dashes
548
(make-string (+ max-length distance -1) :initial-element #\-)
549
" "
550
(symbol-classification-string '#:foo))
551
(:newline)
552
,@(loop for symbol in symbols appending
553
(multiple-value-bind (symbol-string classification-string)
554
(string-representations symbol)
555
`((:value ,symbol ,symbol-string) ,classification-string
556
(:newline)
557
)))))))
558
559
(defmethod make-symbols-listing ((grouping-kind (eql :classification)) symbols)
560
"For each possible classification (cf. CLASSIFY-SYMBOL), group
561
all the symbols in SYMBOLS to all of their respective
562
classifications. (If a symbol is, for instance, boundp and a
563
generic-function, it'll appear both below the BOUNDP group and
564
the GENERIC-FUNCTION group.) As macros and special-operators are
565
specified to be FBOUNDP, there is no general FBOUNDP group,
566
instead there are the three explicit FUNCTION, MACRO and
567
SPECIAL-OPERATOR groups."
568
(let ((table (make-hash-table :test #'eq))
569
(+default-classification+ :misc))
570
(flet ((normalize-classifications (classifications)
571
(cond ((null classifications) `(,+default-classification+))
572
;; Convert an :FBOUNDP in CLASSIFICATIONS to :FUNCTION if possible.
573
((and (member :fboundp classifications)
574
(not (member :macro classifications))
575
(not (member :special-operator classifications)))
576
(substitute :function :fboundp classifications))
577
(t (remove :fboundp classifications)))))
578
(loop for symbol in symbols do
579
(loop for classification in (normalize-classifications (classify-symbol symbol))
580
;; SYMBOLS are supposed to be sorted alphabetically;
581
;; this property is preserved here except for reversing.
582
do (push symbol (gethash classification table)))))
583
(let* ((classifications (loop for k being each hash-key in table collect k))
584
(classifications (sort classifications
585
;; Sort alphabetically, except +DEFAULT-CLASSIFICATION+
586
;; which sort to the end.
587
#'(lambda (a b)
588
(cond ((eql a +default-classification+) nil)
589
((eql b +default-classification+) t)
590
(t (string< a b)))))))
591
(loop for classification in classifications
592
for symbols = (gethash classification table)
593
appending`(,(symbol-name classification)
594
(:newline)
595
,(make-string 64 :initial-element #\-)
596
(:newline)
597
,@(mapcan #'(lambda (symbol)
598
(list `(:value ,symbol ,(symbol-name symbol)) '(:newline)))
599
(nreverse symbols)) ; restore alphabetic orderness.
600
(:newline)
601
)))))
602
603
(defmethod emacs-inspect ((%container %package-symbols-container))
604
(with-struct (%container. title description symbols grouping-kind) %container
605
`(,title (:newline) (:newline)
606
,@description
607
(:newline)
608
" " ,(ecase grouping-kind
609
(:symbol
610
`(:action "[Group by classification]"
611
,(lambda () (setf grouping-kind :classification))
612
:refreshp t))
613
(:classification
614
`(:action "[Group by symbol]"
615
,(lambda () (setf grouping-kind :symbol))
616
:refreshp t)))
617
(:newline) (:newline)
618
,@(make-symbols-listing grouping-kind symbols))))
619
620
(defmethod emacs-inspect ((package package))
621
(let ((package-name (package-name package))
622
(package-nicknames (package-nicknames package))
623
(package-use-list (package-use-list package))
624
(package-used-by-list (package-used-by-list package))
625
(shadowed-symbols (package-shadowing-symbols package))
626
(present-symbols '()) (present-symbols-length 0)
627
(internal-symbols '()) (internal-symbols-length 0)
628
(inherited-symbols '()) (inherited-symbols-length 0)
629
(external-symbols '()) (external-symbols-length 0))
630
631
(do-symbols* (sym package)
632
(let ((status (symbol-status sym package)))
633
(when (eq status :inherited)
634
(push sym inherited-symbols) (incf inherited-symbols-length)
635
(go :continue))
636
(push sym present-symbols) (incf present-symbols-length)
637
(cond ((eq status :internal)
638
(push sym internal-symbols) (incf internal-symbols-length))
639
(t
640
(push sym external-symbols) (incf external-symbols-length))))
641
:continue)
642
643
(setf package-nicknames (sort (copy-list package-nicknames) #'string<)
644
package-use-list (sort (copy-list package-use-list) #'string< :key #'package-name)
645
package-used-by-list (sort (copy-list package-used-by-list) #'string< :key #'package-name)
646
shadowed-symbols (sort (copy-list shadowed-symbols) #'string<))
647
648
(setf present-symbols (sort present-symbols #'string<) ; SORT + STRING-LESSP
649
internal-symbols (sort internal-symbols #'string<) ; conses on at least
650
external-symbols (sort external-symbols #'string<) ; SBCL 0.9.18.
651
inherited-symbols (sort inherited-symbols #'string<))
652
653
654
`("" ; dummy to preserve indentation.
655
"Name: " (:value ,package-name) (:newline)
656
657
"Nick names: " ,@(common-seperated-spec package-nicknames) (:newline)
658
659
,@(when (documentation package t)
660
`("Documentation:" (:newline) ,(documentation package t) (:newline)))
661
662
"Use list: " ,@(common-seperated-spec
663
package-use-list
664
(lambda (package)
665
`(:value ,package ,(package-name package))))
666
(:newline)
667
668
"Used by list: " ,@(common-seperated-spec
669
package-used-by-list
670
(lambda (package)
671
`(:value ,package ,(package-name package))))
672
(:newline)
673
674
,@ ; ,@(flet ((...)) ...) would break indentation in Emacs.
675
(flet ((display-link (type symbols length &key title description)
676
(if (null symbols)
677
(format nil "0 ~A symbols." type)
678
`(:value ,(%make-package-symbols-container :title title
679
:description description
680
:symbols symbols)
681
,(format nil "~D ~A symbol~P." length type length)))))
682
683
`(,(display-link "present" present-symbols present-symbols-length
684
:title (format nil "All present symbols of package \"~A\"" package-name)
685
:description
686
'("A symbol is considered present in a package if it's" (:newline)
687
"\"accessible in that package directly, rather than" (:newline)
688
"being inherited from another package.\"" (:newline)
689
"(CLHS glossary entry for `present')" (:newline)))
690
691
(:newline)
692
,(display-link "external" external-symbols external-symbols-length
693
:title (format nil "All external symbols of package \"~A\"" package-name)
694
:description
695
'("A symbol is considered external of a package if it's" (:newline)
696
"\"part of the `external interface' to the package and" (:newline)
697
"[is] inherited by any other package that uses the" (:newline)
698
"package.\" (CLHS glossary entry of `external')" (:newline)))
699
(:newline)
700
,(display-link "internal" internal-symbols internal-symbols-length
701
:title (format nil "All internal symbols of package \"~A\"" package-name)
702
:description
703
'("A symbol is considered internal of a package if it's" (:newline)
704
"present and not external---that is if the package is" (:newline)
705
"the home package of the symbol, or if the symbol has" (:newline)
706
"been explicitly imported into the package." (:newline)
707
(:newline)
708
"Notice that inherited symbols will thus not be listed," (:newline)
709
"which deliberately deviates from the CLHS glossary" (:newline)
710
"entry of `internal' because it's assumed to be more" (:newline)
711
"useful this way." (:newline)))
712
(:newline)
713
,(display-link "inherited" inherited-symbols inherited-symbols-length
714
:title (format nil "All inherited symbols of package \"~A\"" package-name)
715
:description
716
'("A symbol is considered inherited in a package if it" (:newline)
717
"was made accessible via USE-PACKAGE." (:newline)))
718
(:newline)
719
,(display-link "shadowed" shadowed-symbols (length shadowed-symbols)
720
:title (format nil "All shadowed symbols of package \"~A\"" package-name)
721
:description nil))))))
722
723
724
(defmethod emacs-inspect ((pathname pathname))
725
`(,(if (wild-pathname-p pathname)
726
"A wild pathname."
727
"A pathname.")
728
(:newline)
729
,@(label-value-line*
730
("Namestring" (namestring pathname))
731
("Host" (pathname-host pathname))
732
("Device" (pathname-device pathname))
733
("Directory" (pathname-directory pathname))
734
("Name" (pathname-name pathname))
735
("Type" (pathname-type pathname))
736
("Version" (pathname-version pathname)))
737
,@ (unless (or (wild-pathname-p pathname)
738
(not (probe-file pathname)))
739
(label-value-line "Truename" (truename pathname)))))
740
741
(defmethod emacs-inspect ((pathname logical-pathname))
742
(append
743
(label-value-line*
744
("Namestring" (namestring pathname))
745
("Physical pathname: " (translate-logical-pathname pathname)))
746
`("Host: "
747
,(pathname-host pathname)
748
" (" (:value ,(logical-pathname-translations
749
(pathname-host pathname)))
750
"other translations)"
751
(:newline))
752
(label-value-line*
753
("Directory" (pathname-directory pathname))
754
("Name" (pathname-name pathname))
755
("Type" (pathname-type pathname))
756
("Version" (pathname-version pathname))
757
("Truename" (if (not (wild-pathname-p pathname))
758
(probe-file pathname))))))
759
760
(defmethod emacs-inspect ((n number))
761
`("Value: " ,(princ-to-string n)))
762
763
(defun format-iso8601-time (time-value &optional include-timezone-p)
764
"Formats a universal time TIME-VALUE in ISO 8601 format, with
765
the time zone included if INCLUDE-TIMEZONE-P is non-NIL"
766
;; Taken from http://www.pvv.ntnu.no/~nsaa/ISO8601.html
767
;; Thanks, Nikolai Sandved and Thomas Russ!
768
(flet ((format-iso8601-timezone (zone)
769
(if (zerop zone)
770
"Z"
771
(multiple-value-bind (h m) (truncate (abs zone) 1.0)
772
;; Tricky. Sign of time zone is reversed in ISO 8601
773
;; relative to Common Lisp convention!
774
(format nil "~:[+~;-~]~2,'0D:~2,'0D"
775
(> zone 0) h (round (* 60 m)))))))
776
(multiple-value-bind (second minute hour day month year dow dst zone)
777
(decode-universal-time time-value)
778
(declare (ignore dow dst))
779
(format nil "~4,'0D-~2,'0D-~2,'0DT~2,'0D:~2,'0D:~2,'0D~:[~*~;~A~]"
780
year month day hour minute second
781
include-timezone-p (format-iso8601-timezone zone)))))
782
783
(defmethod emacs-inspect ((i integer))
784
(append
785
`(,(format nil "Value: ~D = #x~8,'0X = #o~O = #b~,,' ,8:B~@[ = ~E~]"
786
i i i i (ignore-errors (coerce i 'float)))
787
(:newline))
788
(when (< -1 i char-code-limit)
789
(label-value-line "Code-char" (code-char i)))
790
(label-value-line "Integer-length" (integer-length i))
791
(ignore-errors
792
(label-value-line "Universal-time" (format-iso8601-time i t)))))
793
794
(defmethod emacs-inspect ((c complex))
795
(label-value-line*
796
("Real part" (realpart c))
797
("Imaginary part" (imagpart c))))
798
799
(defmethod emacs-inspect ((r ratio))
800
(label-value-line*
801
("Numerator" (numerator r))
802
("Denominator" (denominator r))
803
("As float" (float r))))
804
805
(defmethod emacs-inspect ((f float))
806
(cond
807
((> f most-positive-long-float)
808
(list "Positive infinity."))
809
((< f most-negative-long-float)
810
(list "Negative infinity."))
811
((not (= f f))
812
(list "Not a Number."))
813
(t
814
(multiple-value-bind (significand exponent sign) (decode-float f)
815
(append
816
`("Scientific: " ,(format nil "~E" f) (:newline)
817
"Decoded: "
818
(:value ,sign) " * "
819
(:value ,significand) " * "
820
(:value ,(float-radix f)) "^" (:value ,exponent) (:newline))
821
(label-value-line "Digits" (float-digits f))
822
(label-value-line "Precision" (float-precision f)))))))
823
824
(defun make-pathname-ispec (pathname position)
825
`("Pathname: "
826
(:value ,pathname)
827
(:newline) " "
828
,@(when position
829
`((:action "[visit file and show current position]"
830
,(lambda () (ed-in-emacs `(,pathname :charpos ,position)))
831
:refreshp nil)
832
(:newline)))))
833
834
(defun make-file-stream-ispec (stream)
835
;; SBCL's socket stream are file-stream but are not associated to
836
;; any pathname.
837
(let ((pathname (ignore-errors (pathname stream))))
838
(when pathname
839
(make-pathname-ispec pathname (and (open-stream-p stream)
840
(file-position stream))))))
841
842
(defmethod emacs-inspect ((stream file-stream))
843
(multiple-value-bind (content)
844
(call-next-method)
845
(append (make-file-stream-ispec stream) content)))
846
847
(defmethod emacs-inspect ((condition stream-error))
848
(multiple-value-bind (content)
849
(call-next-method)
850
(let ((stream (stream-error-stream condition)))
851
(append (when (typep stream 'file-stream)
852
(make-file-stream-ispec stream))
853
content))))
854
855
(defun common-seperated-spec (list &optional (callback (lambda (v)
856
`(:value ,v))))
857
(butlast
858
(loop
859
for i in list
860
collect (funcall callback i)
861
collect ", ")))
862
863
(defun inspector-princ (list)
864
"Like princ-to-string, but don't rewrite (function foo) as #'foo.
865
Do NOT pass circular lists to this function."
866
(let ((*print-pprint-dispatch* (copy-pprint-dispatch)))
867
(set-pprint-dispatch '(cons (member function)) nil)
868
(princ-to-string list)))
869
870
(provide :swank-fancy-inspector)
871
872