expr: 18.48%

#lang racket/gui
(provide start-tracking
         require-location?
         get-doc
         get-def
         terminate-record-maintainer
         update
         create
         show-references)
(require erl
         racket/class
         compiler/module-suffix
         "collector.rkt"
         "maintainer-server.rkt"
         "../log.rkt")

(define (start-tracking directory ignore?)
  ; NOTE: `fold-files` reduces about 100MB compare with `find-files`
  ; this is reasonable, since `find-files` build a huge list
  (fold-files (lambda (path kind acc)
                (cond
                  [(ignore? path) (values acc #f)]
                  ; NOTE: should I simply assume `*.rkt` is not a ignored file?
                  [(for/or ([ext (get-module-suffixes)]) (path-has-extension? path ext))
                   (create path)
                   acc]
                  [else acc]))
              #f
              directory
              #t))

(define (internal-name path)
  (string->symbol (path->string path)))

;;; Run a new dynamic genserver to track the file (via given path)
; We need this function when
; 1. A new file is created
; 2. start tracking a new project
; 3. The maintainer of a certain file crash now need a new one
(define (create path)
  (define pid (gen-server:start (new record-maintainer-server%) path))
  (register (internal-name path) pid)
  (log:info "maintainer of path ~a started" path)
  pid)

;;; tell corresponding maintainer update the record
(define (update path)
  (define pid (whereis (internal-name path)))
  (set! pid (if pid pid (create path)))
  (gen-server:cast! pid 'update))

(define (terminate-record-maintainer path)
  (define pid (whereis (internal-name path)))
  (unregister (internal-name path))
  (gen-server:stop pid (format "file ~a is removed" path)))

; require-location? : path path -> list
(define (require-location? path require)
  (define pid (whereis (internal-name path)))
  (set! pid (if pid pid (create path)))
  (gen-server:call pid
                   (list 'require-location? require)))
; get-doc : path pos:exact-integer? -> string
(define (get-doc path pos)
  (define pid (whereis (internal-name path)))
  (set! pid (if pid pid (create path)))
  (gen-server:call pid
                   (list 'get-doc pos)))
; get-def : path pos:exact-integer? -> (or symbol #f)
(define (get-def path pos)
  (define pid (whereis (internal-name path)))
  (set! pid (if pid pid (create path)))
  (gen-server:call pid
                   (list 'get-def pos)))

;; Show references popup list-box
(define (show-references editor filename id [parent #f])
  (define key (list filename id))
  (define references (dict-ref projectwise-references key (set)))

  (cond
    [(set-empty? references)
     (message-box "No References" (format "No references found for ~a in ~a" id filename))]
    [else
     (define references-choice-frame
       (new frame% [label (format "References of ~a" id)] [width 600] [height 400] [parent parent]))
     (define refs (set->list references))
     (define choices
       (for/list ([ref-info (in-list refs)])
         (match-define (list ref-file start _end) ref-info)
         (define line (send editor position-line start))
         (define line-sp (send editor line-start-position line))
         (format "~a:~a:~a" (path->string ref-file) (add1 line) (- start line-sp))))

     (define _list-box
       (new list-box%
            [parent references-choice-frame]
            [label "References:"]
            [choices choices]
            [style '(single)]
            [callback
             (lambda (lb event)
               (when (eq? (send event get-event-type) 'list-box-dclick)
                 (define selection (send lb get-selection))
                 (when selection
                   (match-define (list ref-file start end) (list-ref refs selection))
                   (send references-choice-frame show #f)
                   ; jump to picked reference location
                   (define editor-frame (send+ editor (get-tab) (get-frame)))
                   (prepare-editor-for editor-frame ref-file)
                   (send+ editor-frame (get-editor) (set-position start end))
                   (define line (send editor position-line start))
                   (define line-sp (send editor line-start-position line))
                   (log:info "Jump to reference ~a:~a:~a" ref-file line (- start line-sp)))))]))

     (send references-choice-frame center)
     (send references-choice-frame show #t)
     references-choice-frame]))

(define (prepare-editor-for frame path)
  (define tab-of-path-<?> (send frame find-matching-tab path))
  (if tab-of-path-<?>
      ; when we already have a tab for the path, switch to it
      (send frame change-to-tab tab-of-path-<?>)
      ; when we don't have a tab for the path, open one
      (send frame open-in-new-tab path)))