experimental/namespace-watch.ss
(module namespace-watch mzscheme
  (require (lib "class.ss")
           (lib "list.ss")
           (lib "mred.ss" "mred"))
  (provide watch-namespace)
  
  (define-syntax (testers stx)
    (syntax-case stx ()
      [(_)
       #''()]
      [(_ n . ns)
       (identifier? #'n)
       (with-syntax ([p (datum->syntax-object 
                         #'here 
                         (string->symbol (format "~a?" (syntax-e #'n))))])
         #'(cons (lambda (x) (and (p x) 'n)) (testers . ns)))]
      [(_ expr . ns)
       #'(cons expr (testers ns))]))
  
  (define tag-testers
    (testers boolean number char symbol string bytes vector null port eof-object
             procedure
             box keyword syntax promise hash-table channel
             parameter namespace path regexp custodian 
             list pair))
  
  (define namespace-watcher%
    (class object%
      (init-field namespace)
      (define nsht (make-hash-table))
      (define counter 0)
      
      (define view 
        (new namespace-view% (controller this)))
      (define alarm 
        (new timer% 
             (notify-callback 
              (lambda () (when (send view is-shown?) (update))))
             (interval 2000)))
      
      (define/private (update)
        (define nslist null)
        (define change? #f)
        (for-each (lambda (s)
                    (let ([p (hash-table-get nsht s falsep)]
                          [info (get-info s)])
                      (if (and p (equal? (cdr p) info))
                          (set! nslist (cons (cons s p) nslist))
                          (begin
                            (set! nslist (cons (cons s (cons counter info)) nslist))
                            (hash-table-put! nsht s (cons counter info))
                            (set! change? #t)))))
                  (namespace-mapped-symbols namespace))
        (when change?
          (set! counter (add1 counter))
          (send view update (sort nslist))))
      
      (define/private (sort nslist)
        (quicksort nslist psymbol<?))
      
      (define/private (get-info sym)
        (parameterize ([current-namespace namespace])
          (let ([info (identifier-binding (namespace-symbol->identifier sym))]
                [type (get-type sym)])
            (cond [(eq? info #f)
                   (format "global ~a" type)]
                  [(list? info)
                   (format "imported from ~a, ~a" (mpi-fix (car info)) type)]
                  [else "(internal error)"]))))
      
      (define/private (get-type sym)
        (let/ec escape
          (parameterize ([current-namespace namespace])
            (let ([value (namespace-variable-value 
                          sym 
                          #t
                          (lambda () (escape "bound as syntax")))])
              (or (ormap (lambda (p) (p value))
                         tag-testers)
                  'unknown)))))
      
      (define/private (mpi-fix x)
        (if (module-path-index? x)
            (let-values ([(path base) (module-path-index-split x)]) path)
            x))
      
      (super-new)
      (update)))
  
  (define namespace-view%
    (class object%
      (init-field controller)
      (define frame
        (new frame%
             (label "Namespace watcher")
             (width 300)
             (height 500)))
      (define text (new text%))
      (define ecanvas (new editor-canvas% (parent frame) (editor text)))
      (send frame show #t)
      
      (define/public (is-shown?)
        (send frame is-shown?))
      
      (define/public (update nslist)
        (let ([start-b (box 0)])
          (send* text
            (begin-edit-sequence)
            (lock #f)
            (get-visible-position-range start-b #f)
            (erase))
          (for-each (lambda (p) 
                      (send* text
                        (change-style name-style)
                        (insert (format "~s" (car p)))
                        (change-style description-style)
                        (insert (format "    ~a~n" (cddr p)))))
                    nslist)
          (send* text
            (scroll-to-position (unbox start-b))
            (lock #t)
            (end-edit-sequence))))
      
      (send text lock #t)
      (super-new)))
  
  (define name-style
    (let ([sd (make-object style-delta%)])
      (send sd set-delta 'change-weight 'bold)
      (send sd set-delta-foreground "blue")
      sd))
  
  (define description-style
    (let ([sd (make-object style-delta%)])
      (send sd set-delta 'change-weight 'normal)
      (send sd set-delta-foreground "black")
      sd))
  
  (define (falsep) #f)

  (define (psymbol<? a b)
    (or (< (cadr a) (cadr b))
        (and (= (cadr a) (cadr b))
             (string<? (symbol->string (car a)) (symbol->string (car b))))))
  
  (define (watch-namespace ns)
    (new namespace-watcher% (namespace ns)))
  )