(in-package :cl-user)

#| ######################################################################

 Read-eval-inspect

Copyright © 1994-97 Michael Travers 

Permission is given to use and modify this code
as long as the copyright notice is preserved.

Send questions, comments, and fixes to mt@media.mit.edu.

-------------------------------------------------------------------------

This does two things:  

1) results from the Listener get brought into a special inspector window (you
can control what classes of objects get this treatment). 

2) The variable % will always be equal to the object in the topmost inspector.

Interface:
- There is a checked menu item, Auto Inspect, added to the Eval menu.
- see the variables  *readloop-inspector-yes-classes* and *readloop-inspector-no-classes*
to control what types of object get displayed.

Todo:

- if item is already in an inspect window, just update it and bring it front
- turn off during buffer evals

History:
7/6/97 12:42   added *resample-on-select* variable

###################################################################### |#

(defmethod inspect-new-thing ((w inspector::inspector-window) thing)
  (catch :cancel
    (inspector::install-new-inspector (view-container (inspector::inspector-view w) )
                                      (inspector::make-inspector thing))
    (set-window-layer w (1+ *windoid-count*))))
   
(defvar *readloop-inspector* nil)

;;; Access through this function to ensure the inspect window remains valid
(defun readloop-inspector ()
  (if (and *readloop-inspector*
           (slot-value *readloop-inspector* 'wptr))
    *readloop-inspector*
    (multiple-value-bind (pos size) (inspector-position-and-size)
      (setq *readloop-inspector* 
            (make-instance 'inspector::inspector-window 
                           :inspector (make-instance 'inspector::usual-inspector
                                                     :object "Welcome to read-eval-inspect")
                           :view-position pos
                           :view-size size))
      ;; Init arg doesn't work.
      (set-view-size *readloop-inspector* size)
      *readloop-inspector*)))

(defun inspector-position-and-size ()
  (let* ((l-position (view-position *top-listener*))
         (l-size (view-size *top-listener*)))
    (values (+ l-position (point-h l-size))
            (make-point (- *screen-width* (+ (point-h l-position) (point-h l-size)))
                        (point-v l-size)))))

;;; Refinement - only inspect some classes

;;; To be displayed, an object must be a subclass of some yes class, and also not be
;;; a subclass of all no classes.  Got that?  If the yes-class list is nil all checks
;;; are skipped.

;;; The theory behind these values is we want to see anything with structure not obvious from 
;;; its printed representation, but not every method definition because there are a lot of them and 
;;; they usually aren't interesting.
(defparameter *readloop-inspector-yes-classes* '(standard-object structure macptr cons array))
(defparameter *readloop-inspector-no-classes* '(standard-method string))

;;; Menu control

(defvar *readloop-inspector-on* t)

(defvar *readloop-inspect-menu-item* 
  (let ((it (make-instance 'menu-item
                           :menu-item-title "Auto Inspect"
                           :menu-item-action 'readloop-inspect-toggle)))
    (add-menu-items *eval-menu*
                    (make-instance 'menu-item :menu-item-title "-" :disabled t) 
                    it)
    (set-menu-item-check-mark it *readloop-inspector-on*)
    it))

(defun readloop-inspect-toggle ()
  (setq *readloop-inspector-on* (not *readloop-inspector-on*))
  (set-menu-item-check-mark *readloop-inspect-menu-item* *readloop-inspector-on*))


(defun maybe-inspect-new-thing (thing)
  (when (and *readloop-inspector-on*
             (and (or (null *readloop-inspector-yes-classes*)
                      ;; I wonder if this is actually guaranteed valid in CL...
                      (find thing *readloop-inspector-yes-classes* :test #'typep))
                  (or (null *readloop-inspector-no-classes*)
                      (not (find thing *readloop-inspector-no-classes* :test #'typep)))))
    (inspect-new-thing (readloop-inspector) thing)))

;;; Perhaps this could use evalhook instead of relying on an unadvertised function.
(ccl:advise ccl::toplevel-eval
             (progn
               (without-interrupts
                (maybe-inspect-new-thing (caar values))))
             :when :after :name :read-eval-inspect)

;;; % feature
;;; separable from read-eval-inspect, actually
(defvar ccl::% nil "The value in the top inspect window.")

(export 'ccl::% :ccl)

(defvar *resample-on-select* t)

(defmethod window-select :after ((w inspector::inspector-window))
  (when *resample-on-select*
    (inspector::resample w))
  (setq ccl::% (inspector::inspector-object w)))

(advise inspector::push-inspector-history 
        (setq ccl::% (top-inspect-form))
        :when :after :name set-%)

;;; First impressions are so important
(eval-when (eval load)
  (setq ccl::% (top-inspect-form)))

;;; new:  update inspect history automatically
(advise inspector::push-inspector-history 
        (let ((ihw (find-window "inspector history")))
          (when ihw
            (inspector::resample ihw)))
        :when :after :name update-history-window)

;;; just like the system definition, except we return nil instead of the new window (original
;;; behavior is confusing).  The only time this ought to be used is for objects that don't usually
;;; autoinspect.
(defun inspector::inspect (object)
  (inspector::inspect-object object)
  nil)

(provide :read-eval-inspect)
