Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1006 lines
42 KiB

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