(module debugger mzscheme
(require
(lib "class.ss")
(lib "list.ss")
(lib "string.ss")
(lib "bitmap-label.ss" "mrlib")
(lib "framework.ss" "framework")
(lib "mred.ss" "mred")
(prefix dd: "debugger-defs.ss")
(prefix ocaml: "util.ss"))
(provide
definitions-text-mixin
tab-mixin
unit-frame-mixin)
(define (definitions-text-mixin drscheme:unit:definitions-text<%> get-settings-thunk)
(mixin (drscheme:unit:definitions-text<%> scheme:text<%> ocaml:definitions-text<%>) ()
(inherit
backward-match
classify-position
forward-match
get-tab
get-text
get-top-level-window
highlight-range
last-position
scroll-to-position)
(define ocaml:debug:in-handler #f)
(define ocaml:debug:err-handler #f)
(define ocaml:debug-current-time #f)
(define ocaml:debug-current-direction #f)
(define ocaml:debug-highlight-start #f)
(define ocaml:debug-highlight-end #f)
(define ocaml:debug-unhighlight-thunk #f)
(define ocaml:debug-value-highlight-start #f)
(define ocaml:debug-value-highlight-end #f)
(define ocaml:debug-value-unhighlight-thunk #f)
(define ocaml:debug-breakpoints (make-hash-table))
(define bp-pen (send the-pen-list find-or-create-pen "black" 1 'solid))
(define bp-brush (send the-brush-list find-or-create-brush "red" 'solid))
(define bp-mo-pen (send the-pen-list find-or-create-pen "darkgray" 1 'solid))
(define bp-mo-brush (send the-brush-list find-or-create-brush "pink"
'solid))
(define bp-tmp-pen (send the-pen-list find-or-create-pen "black" 1 'solid))
(define bp-tmp-brush (send the-brush-list find-or-create-brush "yellow"
'solid))
(define pc-pen (send the-pen-list find-or-create-pen "black" 1 'solid))
(define pc-brush (send the-brush-list find-or-create-brush "forestgreen" 'solid))
(define pc-err-pen (send the-pen-list find-or-create-pen "black" 1 'solid))
(define pc-err-brush (send the-brush-list find-or-create-brush "red" 'solid))
(define pc-brk-pen (send the-pen-list find-or-create-pen "black" 1 'solid))
(define pc-brk-brush (send the-brush-list find-or-create-brush "gray" 'solid))
(super-new)
(define/public (ocaml:debug:get-breakpoints) ocaml:debug-breakpoints)
(define (average . values)
(/ (apply + values) (length values)))
(define/augment (after-set-next-settings settings)
(inner (void) after-set-next-settings settings)
(let ([frame (send (get-tab) get-frame)])
(when frame
(send frame ocaml:update-button-visibility/settings settings))))
(define/public (ocaml:get-direction) ocaml:debug-current-direction)
(define/public (ocaml:debug:set-in-handler handler)
(set! ocaml:debug:in-handler handler))
(define/public (ocaml:debug:set-err-handler handler)
(set! ocaml:debug:err-handler handler))
(define/public (ocaml:debug:pause-handlers)
(when ocaml:debug:in-handler
(thread-suspend ocaml:debug:in-handler))
(when ocaml:debug:err-handler
(thread-suspend ocaml:debug:err-handler)))
(define/public (ocaml:debug:resume-handlers)
(when ocaml:debug:in-handler
(thread-resume ocaml:debug:in-handler))
(when ocaml:debug:err-handler
(thread-resume ocaml:debug:err-handler)))
(define/public (ocaml:set-debug-time time)
(let ([old-time (or ocaml:debug-current-time 0)]
[new-time (or time 0)])
(set! ocaml:debug-current-time time)
(if (< old-time new-time)
(set! ocaml:debug-current-direction 'forward)
(set! ocaml:debug-current-direction 'backward))))
(define/public (ocaml:set-debug-status message)
(define frame (send (get-tab) get-frame))
(if ocaml:debug-current-time
(send frame update-status-line 'ocaml:debugger
(clean-status (format "(Time: ~a) ~a" ocaml:debug-current-time message)))
(send frame update-status-line 'ocaml:debugger (clean-status message))))
(define/augment (ocaml:clean-up)
(inner (void) ocaml:clean-up)
(ocaml:reset-highlighting)
(set! ocaml:debug:in-handler #f)
(set! ocaml:debug:err-handler #f)
(set! ocaml:debug-current-time #f)
(set! ocaml:debug-current-direction #f)
(send (get-tab) ocaml:kill-debug)
(send (send (get-tab) get-frame) ocaml:hide-debug))
(inherit
dc-location-to-editor-location
editor-location-to-dc-location
invalidate-bitmap-cache
get-canvas)
(define/private (find-char-box text left-pos right-pos)
(let ([xlb (box 0)]
[ylb (box 0)]
[xrb (box 0)]
[yrb (box 0)])
(send text position-location left-pos xlb ylb #t)
(send text position-location right-pos xrb yrb #f)
(let*-values ([(xl-off yl-off) (send text editor-location-to-dc-location
(unbox xlb) (unbox ylb))]
[(xl yl) (dc-location-to-editor-location xl-off yl-off)]
[(xr-off yr-off) (send text editor-location-to-dc-location
(unbox xrb) (unbox yrb))]
[(xr yr) (dc-location-to-editor-location xr-off yr-off)])
(values xl yl xr yr))))
(define (clean-status s)
(substring (regexp-replace* #rx"\n" s " ") 0 (min (string-length s) 200)))
(define/override (on-event event)
(if (ocaml:lang-settings? (get-settings-thunk))
(cond
[(send event button-down? 'right)
(let-values ([(pos text) (ocaml:get-pos/text event this)])
(if (and pos text)
(let ([menu (make-object popup-menu% #f)]
[break-status (hash-table-get ocaml:debug-breakpoints pos (lambda () #f))])
(make-object menu-item%
(if break-status
"Remove pause at this point"
"Pause at this point")
menu
(lambda (item evt)
(hash-table-put! ocaml:debug-breakpoints pos (not break-status))
(when (send (get-tab) ocaml:get-debug-process)
(dd:update-breakpoints (get-tab) ocaml:debug-breakpoints))
(invalidate-bitmap-cache)))
(send (get-canvas) popup-menu menu
(+ 1 (inexact->exact (floor (send event get-x))))
(+ 1 (inexact->exact (floor (send event get-y))))))
(super on-event event)))]
[(send (get-tab) ocaml:get-debug-process)
(cond
[(or (send event moving?) (send event leaving?))
(let-values ([(pos text) (ocaml:get-pos/text event this)])
(when pos
(ocaml:jump-to-debug-token pos)))
(super on-event event)]
[(send event button-down? 'middle)]
[(send event button-up? 'middle)
(let-values ([(pos text) (ocaml:get-pos/text event this)])
(when pos
(ocaml:set-breakpoint pos)))]
[else (super on-event event)])]
[else (super on-event event)])
(super on-event event)))
(define/override (on-paint before dc left top right bottom dx dy draw-caret)
(super on-paint before dc left top right bottom dx dy draw-caret)
(when (not before)
(hash-table-for-each
ocaml:debug-breakpoints
(lambda (pos enabled?)
(when (and (>= pos 0) enabled? (or enabled? (and mouse-over-pos (= mouse-over-pos pos))))
(let*-values ([(xl yl xr yr) (find-char-box this (sub1 pos) pos)]
[(diameter) (max 0 (- xr xl))]
[(yoff) (/ (- yr yl diameter) 2)])
(let ([op (send dc get-pen)]
[ob (send dc get-brush)])
(case enabled?
[(#t) (send dc set-pen bp-pen)
(send dc set-brush bp-brush)]
[(#f) (send dc set-pen bp-mo-pen)
(send dc set-brush bp-mo-brush)]
[else (send dc set-pen bp-tmp-pen)
(send dc set-brush bp-tmp-brush)])
(send dc draw-ellipse (+ xl dx) (+ yl dy yoff) diameter diameter)
(send dc draw-polygon stop-sign
(+ xl dx)
(+ yl dy 2))
(send dc set-pen op)
(send dc set-brush ob)))))))
(let ([pos (send (get-tab) get-pc)])
(when pos
(let*-values ([(xl yl xr yr) (find-char-box this (sub1 pos) pos)]
[(ym) (average yl yr)])
(let ([op (send dc get-pen)]
[ob (send dc get-brush)])
(case (send (get-tab) get-break-status)
[(error) (send dc set-pen pc-err-pen)
(send dc set-brush pc-err-brush)]
[(break) (send dc set-pen pc-brk-pen)
(send dc set-brush pc-brk-brush)]
[else (send dc set-pen pc-pen)
(send dc set-brush pc-brush)]))
(drscheme:arrow:draw-arrow dc xl ym xr ym dx dy))
)))
(define/public (ocaml:jump-to-debug-token pos)
(define id-end (forward-match pos (last-position)))
(define id-start (and id-end (backward-match id-end 0)))
(when
(and
id-start
(> id-end pos)
(eq? 'identifier (classify-position id-start)))
(ocaml:set-debug-value-highlighting id-start id-end)
(dd:inspect-value (get-tab) (get-text id-start id-end))))
(define/public (ocaml:set-breakpoint pos)
(define-values (num address line-num start-char end-char)
(dd:update-breakpoints (get-tab) pos))
(highlight-range pos (add1 pos) (make-object color% "Red"))
(scroll-to-position pos)
(define id-end (forward-match pos (last-position)))
(define id-start (and id-end (backward-match id-end 0)))
(when
(and
id-start
(> id-end pos)
(eq? 'identifier (classify-position id-start)))
(ocaml:set-debug-highlighting id-start id-end)
(dd:inspect-value (get-tab) (get-text id-start id-end))))
(define/augment (ocaml:reset-highlighting)
(define frame (send (get-tab) get-frame))
(inner (void) ocaml:reset-highlighting)
(when ocaml:debug-unhighlight-thunk
(ocaml:debug-unhighlight-thunk)
(set! ocaml:debug-highlight-start #f)
(set! ocaml:debug-highlight-end #f)
(set! ocaml:debug-unhighlight-thunk #f))
(ocaml:reset-minor-debug-highlighting))
(define/public (ocaml:reset-minor-debug-highlighting)
(define frame (send (get-tab) get-frame))
(when ocaml:debug-value-unhighlight-thunk
(ocaml:debug-value-unhighlight-thunk)
(set! ocaml:debug-value-highlight-start #f)
(set! ocaml:debug-value-highlight-end #f)
(set! ocaml:debug-value-unhighlight-thunk #f))
(send frame open-status-line 'ocaml:debugger)
(send frame update-status-line 'ocaml:debugger #f))
(define/public (ocaml:set-debug-value-highlighting start end)
(unless (and
(eq? ocaml:debug-value-highlight-start start)
(eq? ocaml:debug-value-highlight-end end))
(ocaml:reset-minor-debug-highlighting)
(set! ocaml:debug-value-highlight-start start)
(set! ocaml:debug-value-highlight-end end)
(set! ocaml:debug-value-unhighlight-thunk
(highlight-range
start end
(make-object color% "MediumGoldenrod")
#f #f 'high))))
(define/public (ocaml:set-debug-highlighting start end)
(unless (and
(eq? ocaml:debug-highlight-start start)
(eq? ocaml:debug-highlight-end end))
(ocaml:reset-highlighting)
(set! ocaml:debug-highlight-start start)
(set! ocaml:debug-highlight-end end)
(set! ocaml:debug-unhighlight-thunk
(highlight-range start end (make-object color% "lavender")))))))
(define (tab-mixin drscheme:unit:tab<%>)
(mixin (drscheme:unit:tab<%>) ()
(inherit get-frame)
(define ocaml:in-debug #f)
(define ocaml:debug-process #f)
(super-new)
(define/override (break-callback)
(ocaml:kill-debug)
(send (get-frame) ocaml:hide-debug)
(super break-callback))
(define/public (ocaml:kill-debug)
(when (ocaml:process? ocaml:debug-process)
(let ([proc (ocaml:process-proc ocaml:debug-process)])
(subprocess-kill proc #f)
(subprocess-kill proc #t)))
(set! ocaml:debug-process #f))
(define/public (ocaml:get-debug-process) ocaml:debug-process)
(define/public (ocaml:set-debug-process process-obj)
(set! ocaml:debug-process process-obj))))
(define (unit-frame-mixin drscheme:unit:frame<%> get-settings-thunk get-lang)
(mixin (drscheme:unit:frame<%>) (ocaml:unit:frame<%>)
(inherit
get-button-panel
get-definitions-canvas
get-definitions-text
get-interactions-text
get-current-tab
open-status-line
close-status-line
update-status-line
save)
(define ocaml:debug-parent-panel 'uninitialized-debug-parent-panel)
(define ocaml:debug-panel 'uninitialized-debug-panel)
(define/override (get-definitions/interactions-panel-parent)
(set! ocaml:debug-parent-panel
(make-object vertical-panel%
(super get-definitions/interactions-panel-parent)))
(set! ocaml:debug-panel (instantiate horizontal-panel% ()
(parent ocaml:debug-parent-panel)
(stretchable-height #f)
(alignment '(center center))
(style '(border))))
(send ocaml:debug-parent-panel change-children (λ (l) null))
(instantiate button% ()
(label "Hide")
(parent ocaml:debug-panel)
(callback (λ (x y) (ocaml:hide-debug)))
(stretchable-height #t))
(make-object vertical-panel% ocaml:debug-parent-panel))
(super-new)
(define ocaml:debugger-button-parent-panel
(new horizontal-panel%
[parent (get-button-panel)]
[stretchable-width #f]
[stretchable-height #f]))
(define ocaml:debugger-button
(new button%
[label "Save and Debug"]
[parent ocaml:debugger-button-parent-panel]
[callback
(λ (button evt)
(save)
(dd:debug-callback
(get-definitions-text)
(send button get-parent)
(get-settings-thunk)))]))
(define/public (ocaml:hide-debug)
(when (and (object? ocaml:debug-parent-panel)
(member ocaml:debug-panel (send ocaml:debug-parent-panel get-children)))
(send ocaml:debug-parent-panel change-children
(λ (l) (remq ocaml:debug-panel l)))))
(define/public (ocaml:show-debug)
(unless (member ocaml:debug-panel (send ocaml:debug-parent-panel get-children))
(send ocaml:debug-parent-panel change-children
(λ (l) (cons ocaml:debug-panel l)))))
(define/override (execute-callback)
(when (eq? (system-type 'os) 'windows)
(let-values ([(proc in out err)
(subprocess #f #f #f "c:\\cygwin\\bin\\killall.exe" "-v" "-9" "ocamlrun")])
(subprocess-wait proc)
(sleep 0.1)))
(send (get-current-tab) ocaml:kill-debug)
(super execute-callback))
(define/augment (on-close)
(inner (void) on-close)
(when (eq? (system-type 'os) 'windows)
(let-values ([(proc in out err)
(subprocess #f #f #f "c:\\cygwin\\bin\\killall.exe" "-v" "-9" "ocamlrun")])
(subprocess-wait proc)
(sleep 0.1))))
(define ocaml:reverse-button
(instantiate button% ()
[label "Reverse"]
[parent ocaml:debug-panel]
[callback
(λ (button evt)
(dd:repeat-action-callback (get-current-tab) "reverse" 'backward))]
[enabled #t]))
(define ocaml:previous-button
(instantiate button% ()
[label "Previous"]
[parent ocaml:debug-panel]
[callback
(λ (button evt)
(dd:repeat-action-callback (get-current-tab) "previous" 'backward))]
[enabled #t]))
(define ocaml:start-button
(instantiate button% ()
[label "Start"]
[parent ocaml:debug-panel]
[callback
(λ (button evt)
(dd:repeat-action-callback (get-current-tab) "start" 'backward))]
[enabled #t]))
(define ocaml:backstep-button
(instantiate button% ()
[label "Backstep"]
[parent ocaml:debug-panel]
[callback
(λ (button evt)
(dd:repeat-action-callback (get-current-tab) "backstep" 'backward))]
[enabled #t]))
(define ocaml:break-button
(instantiate button% ()
[label "Break"]
[parent ocaml:debug-panel]
[callback (λ (button evt) (dd:break-callback (get-current-tab)))]
[enabled #t]))
(define ocaml:step-button
(instantiate button% ()
[label "Step"]
[parent ocaml:debug-panel]
[callback
(λ (button evt)
(dd:repeat-action-callback (get-current-tab) "step" 'forward))]
[enabled #t]))
(define ocaml:finish-button
(instantiate button% ()
[label "Finish"]
[parent ocaml:debug-panel]
[callback
(λ (button evt)
(dd:repeat-action-callback (get-current-tab) "finish" 'forward))]
[enabled #t]))
(define ocaml:next-button
(instantiate button% ()
[label "Next"]
[parent ocaml:debug-panel]
[callback
(λ (button evt)
(dd:repeat-action-callback (get-current-tab) "next" 'forward))]
[enabled #t]))
(define ocaml:run-button
(instantiate button% ()
[label "Run"]
[parent ocaml:debug-panel]
[callback
(λ (button evt)
(dd:repeat-action-callback (get-current-tab) "run" 'forward))]
[enabled #t]))
(define/augment (on-tab-change old-tab new-tab)
(inner (void) on-tab-change old-tab new-tab)
(ocaml:update-button-visibility/tab new-tab))
(define/public (ocaml:update-button-visibility/tab tab)
(ocaml:update-button-visibility/settings (send (send tab get-defs) get-next-settings)))
(define/pubment (ocaml:update-button-visibility/settings settings)
(inner (void) ocaml:update-button-visibility/settings settings)
(when (object? ocaml:debugger-button-parent-panel)
(let ([visible? (send (get-lang settings) capability-value 'ocaml:debug-button)])
(send ocaml:debugger-button-parent-panel change-children
(λ (l)
(if visible?
(list ocaml:debugger-button)
'()))))))
(define/public (ocaml:debugger:get-button) ocaml:debugger-button)
(send (get-button-panel) change-children
(λ (l)
(cons ocaml:debugger-button-parent-panel
(remove ocaml:debugger-button-parent-panel l))))
(ocaml:update-button-visibility/tab (get-current-tab)))))