Module: environment-debugger Author: Bill Chiles, Jason Trenouth, Andy Armstrong Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// (internal) define sealed class () sealed constant slot command-label :: = "that", init-keyword: label:; sealed constant slot progress-format-string :: = "", init-keyword: format-string:; sealed constant slot progress-format-arguments :: = #[], init-keyword: format-arguments:; end class; /// (internal) define sealed class () end class; /// (internal) define sealed class () sealed constant each-subclass slot zoom-command-zoom :: , init-keyword: zoom:; end class; /// (internal) define sealed class () end class; /// (internal) define sealed class () end class; /// MAKE (dylan) define sealed domain make (subclass()); /// INITIALIZE (dylan) define sealed domain initialize (); /// DEBUGGER-COMMANDs /// /// ---*** DEBUGGER: add command-label slots so that we can be more specific /// about a command in a user message define sealed class () keyword format-string: = "Aborting current operation ..."; end class; define sealed class () keyword format-string: = "Aborting entire nested operation ..."; end class; define sealed class () keyword format-string: = "Raising restart chooser ..."; end class; define sealed class () keyword format-string: = "Stepping into next function call ..."; end class; define sealed class () keyword format-string: = "Stepping over next function call ..."; end class; define sealed class () keyword format-string: = "Stepping out of current function call ..."; end class; define sealed class () keyword format-string: = "Raising symbols browser ..."; end class; define sealed class () keyword format-string: = "Raising registers browser ..."; end class; define sealed class () keyword format-string: = "Raising memory browser ..."; end class; define sealed class () keyword format-string: = "Raising value setter ..."; end class; define sealed class () keyword format-string: = "Raising evaluation dialog ..."; end class ; define sealed class () end class; define sealed class () end class; define sealed class () end class; define sealed class () end class; define sealed class () keyword zoom: = #"zoom-debugging"; end class; define sealed class () keyword zoom: = #"zoom-interacting"; end class; define sealed class () end class; define sealed class () end class; define sealed class () end class; define sealed class () end class; define sealed class () end class; /// *THREAD-COMMAND-TABLE* (internal) /// /// NB motley assortment of debugger specific commands define variable $step-over-bitmap :: = "->"; define variable $step-into-bitmap :: = "-v"; define variable $step-out-bitmap :: = "-^"; define constant $step-over-doc = "Steps to current function\'s next known source location (skips over function calls)."; define constant $step-into-doc = "Steps to called function\'s next known source location (enters function calls)"; define constant $step-out-doc = "Steps to caller function\'s next known source location (leaves current function)."; define constant $step-over-title = "Step Over"; define constant $step-into-title = "Step Into"; define constant $step-out-title = "Step Out"; define command-table *thread-command-table* (*global-command-table*) menu-item "Abort" = , documentation: "Aborts current nested operation (without quitting connected application)."; menu-item "Abort All" = , documentation: "Aborts whole operation (without quitting connected application)"; menu-item "Continue..." = , documentation: "Chooses and invokes application restart."; separator; menu-item $step-over-title = , accelerator: make-keyboard-gesture(#"f11"), image: $step-over-bitmap, documentation: $step-over-doc; menu-item $step-into-title = , accelerator: make-keyboard-gesture(#"f12"), image: $step-into-bitmap, documentation: $step-into-doc; menu-item $step-out-title = , accelerator: make-keyboard-gesture(#"f12", #"shift"), image: $step-out-bitmap, documentation: $step-out-doc; separator; menu-item "Select Thread..." = debugger-select-thread, documentation: "Selects a thread for this debugger."; end command-table *thread-command-table*; /// *DEBUGGER-GO-COMMAND-TABLE* (internal) /// /// NB adds stack navigation commands to history navigation ones define variable $top-of-stack-bitmap :: = "|<"; define variable $bottom-of-stack-bitmap :: = "<"; define variable $up-stack-bitmap :: = ">"; define variable $down-stack-bitmap :: = ">|"; /* define constant $top-of-stack-doc = "Selects newest stack frame."; define constant $bottom-of-stack-doc = "Selects oldest stack frame."; define constant $down-stack-doc = "Selects next older stack frame."; define constant $up-stack-doc = "Selects next newer stack frame."; define constant $top-of-stack-title = "Top of Stack"; define constant $up-stack-title = "Up Stack"; define constant $down-stack-title = "Down Stack"; define constant $bottom-of-stack-title = "Bottom of Stack"; */ /// *DEBUGGER-ZOOM-COMMANDS-COMMAND-TABLE* (internal) /// /// NB different views of the debugger (eg can just show interactor) /// --- hughg, 1998/01/31: Try something simpler for switching 2 layouts. define command-table *debugger-zoom-commands-command-table* (*global-command-table*) menu-item "Debugging Layout" = ; menu-item "Interacting Layout" = ; end command-table; /// *DEBUGGER-TREE-NODE-COMMAND-TABLE* (internal) define command-table *debugger-tree-node-command-table* (*global-command-table*) menu-item "Expand All" = ; menu-item "Expand" = ; menu-item "Collapse" = ; menu-item "Collapse All" = ; end command-table; /// *DEBUGGER-BAR-OPTIONS-COMMAND-TABLE* (internal) define command-table *debugger-bar-options-command-table* (*global-command-table*) end command-table *debugger-bar-options-command-table*; add-command-table-menu-item (*debugger-bar-options-command-table*, "", , vector(#"tool-bar", #"status-bar", #"context"), items: #[#["Toolbar", #"tool-bar"], #["Status Bar", #"status-bar"], #["Context Window", #"context"]], label-key: first, value-key: second, callback: method (menu-box) frame-show-bars?(sheet-frame(menu-box), gadget-value(menu-box)) end); define method frame-show-bars? (debugger :: , bars :: ) => () let top-sheet = top-level-sheet(debugger); let tool-bar = frame-tool-bar(debugger); let status-bar = frame-status-bar(debugger); let context = debugger-context-pane(debugger); let tool-bar? = member?(#"tool-bar", bars); let status-bar? = member?(#"status-bar", bars); let context? = member?(#"context", bars); let relayout? = #f; local method show-or-hide (sheet, present?) => () // Work extra hard to ensure that everything gets re-layed out, // since bars can have associated "decorations" when (sheet & sheet-withdrawn?(sheet) == present?) sheet-withdrawn?(sheet) := ~present?; for (s = sheet then sheet-parent(s), until: s == top-sheet) sheet-layed-out?(s) := #f end; relayout? := #t end end method; show-or-hide(tool-bar, tool-bar?); show-or-hide(status-bar, status-bar?); show-or-hide(context, context?); when (relayout?) relayout-children(top-sheet); relayout-parent(tool-bar | status-bar | context); sheet-mapped?(tool-bar) := tool-bar?; sheet-mapped?(status-bar) := status-bar?; sheet-mapped?(context) := context?; end end method frame-show-bars?; /// *DEBUGGER-VIEW-COMMAND-TABLE* (internal) /// /// NB adds zoom and tree commands to common view menu. define command-table *debugger-view-command-table* (*global-command-table*) include *debugger-bar-options-command-table*; include *debugger-zoom-commands-command-table*; separator; include *debugger-tree-node-command-table*; separator; include *view-refresh-command-table*; menu-item "Refresh All Debuggers" = , accelerator: make-keyboard-gesture(#"f5", #"alt"), documentation: "Refreshes all debugger windows for current project."; menu-item "Debugger Options..." = frame-edit-options, documentation: "Enables you to change debugger options."; end command-table; /// DEBUGGER-GO-COMMAND-TABLE (internal) define command-table *debugger-go-command-table* (*global-command-table*) include *browse-locations-command-table*; separator; menu-item "Registers" = debugger-show-registers, documentation: "Shows the contents of the current thread's registers"; end command-table *debugger-go-command-table*; /// FRAME-EDIT-OPTIONS (environment-framework) define sealed method frame-edit-options (debugger :: ) => () let dialog = make(, owner: debugger); if (start-dialog(dialog)) update-from-dialog(debugger, dialog) end end method frame-edit-options; /// *DEBUGGER-FILE-COMMAND-TABLE* (internal) define command-table *debugger-file-command-table* (*global-command-table*) menu-item "New Window" = clone-tool, accelerator: make-keyboard-gesture(#"n", #"control"), documentation: "Opens a new debugger window."; //---*** andrewa: removed for 1.0 // menu-item "New Linked Window" = clone-and-link-tool, // documentation: "Opens another debugger window, linked to its selection."; include *export-command-table*; // include *print-command-table*; menu-item "Close" = frame-close-file, accelerator: make-keyboard-gesture(#"f4", #"alt"), documentation: "Closes the debugger window."; end command-table *debugger-file-command-table*; /// *DEBUGGER-COMMAND-TABLE* (internal) define command-table *debugger-command-table* (*global-command-table*) menu-item "File" = *debugger-file-command-table*; menu-item "Edit" = *edit-command-table*; menu-item "View" = *debugger-view-command-table*; menu-item "Go" = *debugger-go-command-table*; menu-item "Project" = *project-command-table*; menu-item "Application" = *run-command-table*; menu-item "Thread" = *thread-command-table*; menu-item "Window" = *windows-command-table*; menu-item "Help" = *environment-help-command-table*; end command-table *debugger-command-table*; /// EXECUTE-DEBUGGER-FUNCTION (internal) define function execute-debugger-function (function :: , debugger :: , #key message :: = "Processing ...") => (#rest values) let return-values :: = #[]; with-busy-cursor (debugger) noting-progress (debugger, message) let (#rest function-values) = function(debugger); return-values := function-values; note-progress (1, 1, label: ""); end; end; apply(values, return-values) end function; /// EXECUTE-COMMAND (duim-frames) define sealed method do-execute-command (debugger :: , command :: ) => (#rest values) execute-debugger-function (curry(execute-debugger-command, command), debugger, message: apply(format-to-string, command.progress-format-string, command.progress-format-arguments)) end method; /// ---*** DEBUGGER: may want to offer option to stop application or /// refresh debugger here or may want to disable commands in menu bars /// and tool bars etc define sealed method do-execute-command (debugger :: , command :: ) => (#rest values) case // ---*** DEBUGGER: probably tested before we get here (if so how do we present message) ~command-enabled?(command, debugger) => environment-error-message (format-to-string ("You need to stop the application in order to do %s.", command.command-label), owner: debugger); ~debugger.debugger-updated? => environment-error-message (format-to-string ("You need to refresh the debugger in order to do %s.", command.command-label), owner: debugger); otherwise => next-method(); end case; end method; /// EXECUTE-DEBUGGER-COMMAND (internal) define sealed method execute-debugger-command (command :: , debugger :: ) abort-application(debugger) end method; define sealed method execute-debugger-command (command :: , debugger :: ) abort-application(debugger, order-restarts: reverse!) end method; define sealed method abort-application (debugger :: , #key order-restarts = identity) let project :: = debugger.frame-project; let thread :: = debugger.debugger-thread; let restarts :: = order-restarts(application-thread-restarts(project, thread)); let abort-restart :: false-or() = application-abort-restart(project, restarts); if (abort-restart) invoke-application-restart(project, thread, abort-restart); else environment-error-message ("No abort restart is defined in this thread.", owner: debugger); end if; end method; define function application-abort-restart (project :: , restarts :: ) => (abort-restart :: false-or()) block (return) for (restart in restarts) if (application-restart-abort?(project, restart)) return(restart); end if; end for; end block; end function; define variable $choose-restart-dialog-width :: false-or() = #f; define variable $choose-restart-dialog-height :: false-or() = #f; define sealed method execute-debugger-command (command :: , debugger :: ) let project = debugger.frame-project; let thread = debugger.debugger-thread; let restarts = application-thread-restarts(project, thread); if (empty?(restarts)) environment-message (format-to-string("No available restarts in %s.", environment-object-display-name(project, thread, #f)), owner: debugger) else let (chosen :: false-or(), success?, width, height) = choose-from-dialog(restarts, title: "Choose Restart", default-item: restarts[0], label-key: curry(application-restart-message, project), width: $choose-restart-dialog-width, height: $choose-restart-dialog-height); when (chosen & success?) $choose-restart-dialog-width := width; $choose-restart-dialog-height := height; invoke-application-restart(project, debugger.debugger-thread, chosen); end end end method; /// ---*** DEBUGGER: these step commands need to have debug-point-handlers define sealed method execute-debugger-command (command :: , debugger :: ) step-application-into(debugger.frame-project, debugger.debugger-thread) end method; define sealed method execute-debugger-command (command :: , debugger :: ) step-application-over(debugger.frame-project, debugger.debugger-thread, stack-frame: debugger.debugger-stepping-stack-frame) end method; define sealed method execute-debugger-command (command :: , debugger :: ) let frame = debugger.debugger-stepping-out-stack-frame; step-application-out(debugger.frame-project, debugger.debugger-thread, stack-frame: frame) end method; define sealed method execute-debugger-command (command :: , debugger :: ) // ---*** DEBUGGER: blat symbols into table in non-modal dialog // ---*** APPLICATION: ask for find-symbols-near-to API end method; define sealed method execute-debugger-command (command :: , debugger :: ) // ---*** DEBUGGER: blat registers in hex into table in non-modal dialog // ---*** ENVIRONMENT: may be add registers tab to project controller? // ---*** APPLICATION: ask for show-registers API end method; define sealed method execute-debugger-command (command :: , debugger :: ) // ---*** DEBUGGER: blat memory in hex into table in non-modal dialog // ---*** ENVIRONMENT: may be add memory tab to project controller? // ---*** APPLICATION: ask for DISPLAY MEMORY API end method; define sealed method execute-debugger-command (command :: , debugger :: ) // ---*** APPLICATION: stopgap: snarf console debugger code to set value in runtime end method; define sealed method execute-debugger-command (command :: , debugger :: ) // ---*** APPLICATION: stopgap: snarf console debugger code to evaluate call in runtime end method; define sealed method execute-debugger-command (command :: , debugger :: ) let top = debugger.debugger-filtered-stack[0]; stack-pane-change-frame(debugger, top); end method; /// ---*** DEBUGGER: this search to find the next frame might be too /// inefficient so could build an up+down table when stack is /// refiltered define sealed method execute-debugger-command (command :: , debugger :: ) let current = debugger-current-stack-frame(debugger); if (current) let filtered-stack = debugger.debugger-filtered-stack; let index = find-key(filtered-stack, curry(\==, current)); let up = filtered-stack[max(0, index - 1)]; stack-pane-change-frame(debugger, up); end end method; define sealed method execute-debugger-command (command :: , debugger :: ) let current = debugger-current-stack-frame(debugger); if (current) let filtered-stack = debugger.debugger-filtered-stack; let index = find-key(filtered-stack, curry(\==, current)); let down = filtered-stack[min(filtered-stack.size - 1, index + 1)]; stack-pane-change-frame(debugger, down); end end method; define sealed method execute-debugger-command (command :: , debugger :: ) let filtered-stack = debugger.debugger-filtered-stack; let bottom = filtered-stack[filtered-stack.size - 1]; stack-pane-change-frame(debugger, bottom); end method; define sealed method execute-debugger-command (command :: , debugger :: ) debugger.debugger-zoom := command.zoom-command-zoom; update-debugger(debugger) end method; define sealed method execute-debugger-command (command :: , debugger :: ) walk-nodes(expand-node, debugger.debugger-stack-gadget); end method; define sealed method execute-debugger-command (command :: , debugger :: ) let tree = debugger.debugger-stack-gadget; let object = tree.gadget-value; if (object) expand-node(tree, find-node(tree, object)); end if; end method; define sealed method execute-debugger-command (command :: , debugger :: ) let tree = debugger.debugger-stack-gadget; let object = tree.gadget-value; if (object) contract-node(tree, find-node(tree, object)); end if; end method; define sealed method execute-debugger-command (command :: , debugger :: ) walk-nodes(contract-node, debugger.debugger-stack-gadget); end method; define sealed method execute-debugger-command (command :: , debugger :: ) refresh-all-debuggers(debugger.frame-project) end method; /// WALK-NODES (internal) /// /// ---*** DUIM: perhaps DUIM should supply WALK-NODES define function walk-nodes (function :: , tree :: ) => () let nqueue = make(); local method walk-tree () unless (empty?(nqueue)) let node = pop(nqueue); function(tree, node); for (child in node-children(node)) push(nqueue, child); end for; walk-tree(); end unless; end method; for (object in tree-control-roots(tree)) push(nqueue, find-node(tree, object)); end for; walk-tree(); end function; /// UPDATE-DEBUGGER-COMMAND-TABLE (internal) /// /// This should take care of menubars and toolbars define function update-debugger-command-table (debugger :: ) local method update (item, comtab :: ) let object = decorator-object(item); let type = decorator-type(item); select (type) , => update-debugger-command(debugger, object); => do-command-table-menu-items(update, object); otherwise => #f; // etc end select; end method; do-command-table-menu-items(update, frame-command-table(debugger)); end function; /// UPDATE-DEBUGGER-COMMAND (internal) define sealed method update-debugger-command (debugger :: , commandoid) => () #f end method update-debugger-command; /// ---*** DEBUGGER: I hope the following enabling and disabling based /// on function equality works define sealed method update-debugger-command (debugger :: , command == refresh-frame) => () command-enabled?(command, debugger) := debugger.debugger-enabled? end method update-debugger-command; define sealed method update-debugger-command (debugger :: , command == debugger-select-thread) => () let can-switch? = ~$debugger-settings.one-debugger-per-thread & debugger.debugger-enabled?; command-enabled?(command, debugger) := can-switch? end method update-debugger-command; define sealed method update-debugger-command (debugger :: , command :: ) => () command-enabled?(command, debugger) := debugger.debugger-enabled? end method update-debugger-command; define sealed method update-debugger-command (debugger :: , command :: ) => () command-enabled?(command, debugger) := debugger.debugger-enabled? & begin let application = debugger.frame-project.project-application; select (application & application.application-state) #"stopped" => #t; otherwise => #f; end end end method update-debugger-command; define sealed method update-debugger-command (debugger :: , command :: ) => () command-enabled?(command, debugger) := debugger.debugger-enabled? & begin let project = debugger.frame-project; let thread = debugger.debugger-thread; let restarts = application-thread-restarts(project, thread); application-abort-restart(project, restarts) end & #t end method update-debugger-command; define sealed method update-debugger-command (debugger :: , command :: ) => () command-enabled?(command, debugger) := debugger.debugger-enabled? & begin let project = debugger.frame-project; let thread = debugger.debugger-thread; ~empty?(application-thread-restarts(project, thread)); end end method update-debugger-command; /// FRAME-NOTE-APPLICATION-STATE-CHANGED /// /// ---*** DEBUGGER: could do these with specialist debugger command subclass define sealed method frame-note-application-state-changed (debugger :: , state :: false-or()) => () next-method(); if (debugger.frame-mapped?) update-debugger-command-table(debugger); end if; end method; /// MAKE-STACK-NAVIGATION-TOOL-BAR-BUTTONS (internal) /*---*** andrewa: We've decided to remove these for the moment define function make-stack-navigation-tool-bar-buttons (frame :: ) => (buttons :: ) vector(make(