kopia lustrzana https://github.com/c9/core
1136 wiersze
43 KiB
JavaScript
1136 wiersze
43 KiB
JavaScript
define(function(require, exports, module) {
|
|
main.consumes = [
|
|
"Panel", "settings", "ui", "immediate", "run", "panels", "tabManager",
|
|
"commands", "dialog.confirm", "dialog.error", "debugger.socket"
|
|
];
|
|
main.provides = ["debugger"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var Panel = imports.Panel;
|
|
var Socket = imports["debugger.socket"];
|
|
var settings = imports.settings;
|
|
var ui = imports.ui;
|
|
var tabs = imports.tabManager;
|
|
var panels = imports.panels;
|
|
var commands = imports.commands;
|
|
var run = imports.run;
|
|
var showError = imports["dialog.error"].show;
|
|
var confirm = imports["dialog.confirm"].show;
|
|
|
|
var Frame = require("../data/frame");
|
|
var Source = require("../data/source");
|
|
var Breakpoint = require("../data/breakpoint");
|
|
var Variable = require("../data/variable");
|
|
var Scope = require("../data/scope");
|
|
var Data = require("../data/data");
|
|
|
|
var markup = require("text!./debugger.xml");
|
|
var css = require("text!./debugger.css");
|
|
|
|
/***** Initialization *****/
|
|
|
|
var plugin = new Panel("Ajax.org", main.consumes, {
|
|
index: options.index || 100,
|
|
caption: "Debugger",
|
|
buttonCSSClass: "debugger",
|
|
panelCSSClass: "debugcontainer",
|
|
minWidth: 165,
|
|
// autohide: true,
|
|
width: 300,
|
|
where: options.where || "right"
|
|
});
|
|
var emit = plugin.getEmitter();
|
|
|
|
var debuggers = {};
|
|
var pauseOnBreaks = 0;
|
|
var state = "disconnected";
|
|
var sources = [];
|
|
var running, activeFrame, dbg, name, process, socket, disabledFeatures;
|
|
|
|
var container, btnResume, btnStepOver, btnStepInto, btnStepOut,
|
|
btnSuspend, btnPause, btnOutput, btnImmediate, btnSnapshots; // ui elements
|
|
|
|
var loaded = false;
|
|
function load() {
|
|
if (loaded) return false;
|
|
loaded = true;
|
|
|
|
settings.on("read", function() {
|
|
settings.setDefaults("user/debug", [
|
|
["pause", "0"],
|
|
["autoshow", "true"]
|
|
]);
|
|
|
|
pauseOnBreaks = settings.getNumber("user/debug/@pause");
|
|
togglePause(pauseOnBreaks);
|
|
});
|
|
|
|
// Register this panel on the left-side panels
|
|
plugin.setCommand({
|
|
name: "toggledebugger",
|
|
hint: "show the debugger panel",
|
|
// bindKey : { mac: "Command-U", win: "Ctrl-U" }
|
|
});
|
|
|
|
// Commands
|
|
|
|
commands.addCommand({
|
|
name: "resume",
|
|
group: "Run & Debug",
|
|
hint: "resume the current paused process",
|
|
bindKey: { mac: "F8|Command-\\", win: "F8" },
|
|
exec: function() {
|
|
dbg && dbg.resume();
|
|
}
|
|
}, plugin);
|
|
commands.addCommand({
|
|
name: "suspend",
|
|
group: "Run & Debug",
|
|
hint: "suspend the current running process",
|
|
// bindKey : {mac: "F8", win: "F8"},
|
|
exec: function() {
|
|
dbg && dbg.suspend();
|
|
}
|
|
}, plugin);
|
|
commands.addCommand({
|
|
name: "stepinto",
|
|
group: "Run & Debug",
|
|
hint: "step into the function that is next on the execution stack",
|
|
bindKey: { mac: "F11|Command-;", win: "F11" },
|
|
exec: function() {
|
|
dbg && dbg.stepInto();
|
|
}
|
|
}, plugin);
|
|
commands.addCommand({
|
|
name: "stepover",
|
|
group: "Run & Debug",
|
|
hint: "step over the current expression on the execution stack",
|
|
bindKey: { mac: "F10|Command-'", win: "F10" },
|
|
exec: function() {
|
|
dbg && dbg.stepOver();
|
|
}
|
|
}, plugin);
|
|
commands.addCommand({
|
|
name: "stepout",
|
|
group: "Run & Debug",
|
|
hint: "step out of the current function scope",
|
|
bindKey: { mac: "Shift-F11|Command-Shift-'", win: "Shift-F11" },
|
|
exec: function() {
|
|
dbg && dbg.stepOut();
|
|
}
|
|
}, plugin);
|
|
|
|
// Load CSS
|
|
ui.insertCss(css, plugin);
|
|
}
|
|
|
|
var drawn;
|
|
function draw(opts) {
|
|
if (drawn) return;
|
|
drawn = true;
|
|
|
|
// Import Skin
|
|
ui.insertSkin({
|
|
name: "debugger",
|
|
data: require("text!./skin.xml"),
|
|
"media-path": options.staticPrefix + "/images/",
|
|
"icon-path": options.staticPrefix + "/icons/"
|
|
}, plugin);
|
|
|
|
// Create UI elements
|
|
var bar = opts.aml;
|
|
|
|
var scroller = bar.$ext.appendChild(document.createElement("div"));
|
|
scroller.className = "scroller";
|
|
|
|
// Create UI elements
|
|
var parent = bar;
|
|
ui.insertMarkup(parent, markup, plugin);
|
|
|
|
container = plugin.getElement("hbox");
|
|
|
|
btnResume = plugin.getElement("btnResume");
|
|
btnStepOver = plugin.getElement("btnStepOver");
|
|
btnStepInto = plugin.getElement("btnStepInto");
|
|
btnStepOut = plugin.getElement("btnStepOut");
|
|
btnSuspend = plugin.getElement("btnSuspend");
|
|
btnPause = plugin.getElement("btnPause");
|
|
btnOutput = plugin.getElement("btnOutput");
|
|
btnImmediate = plugin.getElement("btnImmediate");
|
|
|
|
// @todo move this to F8 and toggle between resume
|
|
// btnSuspend.on("click", function(){
|
|
// suspend();
|
|
// });
|
|
|
|
togglePause(pauseOnBreaks);
|
|
|
|
btnPause.on("click", function() {
|
|
togglePause();
|
|
});
|
|
|
|
btnOutput.on("click", function() {
|
|
commands.exec("showoutput", null, {
|
|
id: name
|
|
});
|
|
});
|
|
|
|
btnImmediate.on("click", function() {
|
|
commands.exec("showimmediate", null, {
|
|
evaluator: "debugger"
|
|
});
|
|
});
|
|
|
|
// Update button state
|
|
plugin.on("stateChange", function(e) {
|
|
state = e.state;
|
|
|
|
updateButtonState(state);
|
|
});
|
|
|
|
updateButtonState(state);
|
|
|
|
emit.sticky("drawPanels", { html: scroller, aml: bar });
|
|
}
|
|
|
|
/***** Methods *****/
|
|
|
|
function updateButtonState(state) {
|
|
if (!drawn)
|
|
return;
|
|
|
|
var notConnected = state == "disconnected" || state == "away";
|
|
|
|
btnResume.$ext.style.display = state == "stopped"
|
|
? "inline-block" : "none";
|
|
btnSuspend.$ext.style.display = notConnected
|
|
|| state != "stopped" ? "inline-block" : "none";
|
|
|
|
btnSuspend.setAttribute("disabled", notConnected);
|
|
btnStepOver.setAttribute("disabled", notConnected || state != "stopped");
|
|
btnStepInto.setAttribute("disabled", notConnected || state != "stopped");
|
|
btnStepOut.setAttribute("disabled", notConnected || state != "stopped");
|
|
btnOutput.setAttribute("disabled", notConnected);
|
|
|
|
if (!dbg) return;
|
|
// can't use visible true since it changes display to block
|
|
btnStepOver.$ext.style.display =
|
|
btnStepInto.$ext.style.display =
|
|
btnStepOut.$ext.style.display = dbg.features.snapshotDebugger ? "none" : "";
|
|
|
|
if (dbg.features.snapshotDebugger) {
|
|
btnResume.$ext.style.display =
|
|
btnSuspend.$ext.style.display = "none";
|
|
updateSnapshotList();
|
|
btnSnapshots.$ext.style.display = "";
|
|
}
|
|
else {
|
|
if (btnSnapshots)
|
|
btnSnapshots.$ext.style.display = "none";
|
|
}
|
|
|
|
btnPause.$ext.style.display = dbg.features.setBreakBehavior ? "" : "none";
|
|
btnImmediate.$ext.style.display = dbg.features.executeCode ? "" : "none";
|
|
}
|
|
|
|
function updateSnapshotList(snapshots) {
|
|
if (!btnSnapshots) {
|
|
btnSnapshots = ui.dropdown({ skin: "black_dropdown", "empty-message": "Waiting for snapshot..." });
|
|
btnResume.parentNode.insertBefore(btnSnapshots, btnResume.parentNode.firstChild);
|
|
plugin.addElement(btnSnapshots);
|
|
btnSnapshots.on("afterchange", function(e) {
|
|
if (dbg.features.snapshotDebugger) {
|
|
dbg.selectSnapshot(e.value && e.value.data);
|
|
dbg.getFrames(function(err, frames) {
|
|
if (!err && frames.length) {
|
|
emit("framesLoad", {
|
|
frames: frames,
|
|
frame: findTopFrame(frames)
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
if (snapshots) {
|
|
while (btnSnapshots.lastChild)
|
|
btnSnapshots.removeChild(btnSnapshots.lastChild);
|
|
snapshots.forEach(function(x) {
|
|
var item = ui.item({ caption: x.caption, value: x });
|
|
btnSnapshots.appendChild(item);
|
|
});
|
|
btnSnapshots.select(btnSnapshots.firstChild);
|
|
}
|
|
}
|
|
|
|
function initializeDebugger() {
|
|
// State Change
|
|
var stateTimer;
|
|
dbg.on("stateChange", function(e) {
|
|
var action = e.state == "running" ? "disable" : "enable";
|
|
|
|
// Wait for 500ms in case we are step debugging
|
|
clearTimeout(stateTimer);
|
|
if (action == "disable")
|
|
stateTimer = setTimeout(function() {
|
|
updatePanels(action, e.state);
|
|
}, 500);
|
|
else {
|
|
updatePanels(action, e.state);
|
|
}
|
|
}, plugin);
|
|
|
|
// Receive the breakpoints on attach
|
|
dbg.on("attach", function(e) {
|
|
e.implementation = dbg;
|
|
togglePause(pauseOnBreaks);
|
|
|
|
emit("attach", e);
|
|
updateButtonState();
|
|
}, plugin);
|
|
|
|
dbg.on("detach", function(e) {
|
|
updateButtonState("detached");
|
|
|
|
//@todo
|
|
emit("detach", e);
|
|
}, plugin);
|
|
|
|
dbg.on("error", function(err) {
|
|
if (!process || !process.checkState) return;
|
|
|
|
process.checkState(function() {
|
|
if (err.code == "ECONNREFUSED" || err.code == "ECONNRESET") {
|
|
// Ignore error if process has stopped
|
|
if (process.running >= process.STARTING)
|
|
showError("Could not connect debugger to the debugger proxy");
|
|
}
|
|
else if (err.code) {
|
|
showError(err.message || "Debugger connection error " + err.code);
|
|
}
|
|
if (process.running >= process.STARTING)
|
|
socket.connect();
|
|
});
|
|
});
|
|
|
|
dbg.on("getBreakpoints", function() {
|
|
return emit("getBreakpoints");
|
|
});
|
|
|
|
// When hitting a breakpoint or exception or stepping
|
|
function startDebugging(e) {
|
|
if (settings.getBool("user/debug/@autoshow"))
|
|
panels.activate("debugger");
|
|
|
|
// Reload Frames
|
|
emit("framesLoad", e);
|
|
|
|
// Process Exception
|
|
if (e.exception) {
|
|
emit("exception", e);
|
|
}
|
|
|
|
emit("break", e);
|
|
}
|
|
dbg.on("break", startDebugging, plugin);
|
|
dbg.on("exception", startDebugging, plugin);
|
|
dbg.on("suspend", function() {
|
|
dbg.getFrames(function(err, frames) {
|
|
if (frames.length) {
|
|
startDebugging({
|
|
frames: frames,
|
|
frame: findTopFrame(frames)
|
|
});
|
|
}
|
|
});
|
|
}, plugin);
|
|
|
|
// When a new frame becomes active
|
|
dbg.on("frameActivate", function(e) {
|
|
activeFrame = e.frame;
|
|
emit("frameActivate", e);
|
|
}, plugin);
|
|
|
|
dbg.on("sources", function(e) {
|
|
sources = e.sources.slice();
|
|
emit("sources", e);
|
|
}, plugin);
|
|
|
|
dbg.on("sourcesCompile", function(e) {
|
|
sources.push(e.source);
|
|
emit("sourcesCompile", e);
|
|
}, plugin);
|
|
|
|
dbg.on("breakpointUpdate", function(e) {
|
|
emit("breakpointUpdate", {
|
|
breakpoint: e.breakpoint
|
|
});
|
|
}, plugin);
|
|
|
|
if (dbg.features.snapshotDebugger) {
|
|
dbg.on("snapshotUpdate", function(e) {
|
|
if (settings.getBool("user/debug/@autoshow"))
|
|
panels.activate("debugger");
|
|
updateSnapshotList(e.snapshots);
|
|
updatePanels("enable", dbg.state);
|
|
}, plugin);
|
|
}
|
|
}
|
|
|
|
function updatePanels(action, runstate) {
|
|
state = running != run.STOPPED && dbg && dbg.attached ? runstate : "disconnected";
|
|
emit("stateChange", { state: state, action: action });
|
|
}
|
|
|
|
function togglePause(force) {
|
|
pauseOnBreaks = force !== undefined
|
|
? force
|
|
: (pauseOnBreaks > 1 ? 0 : pauseOnBreaks + 1);
|
|
|
|
if (btnPause) {
|
|
btnPause.setAttribute("class", "pause" + pauseOnBreaks + " nosize exception_break");
|
|
btnPause.setAttribute("tooltip",
|
|
pauseOnBreaks === 0
|
|
? "Don't pause on exceptions"
|
|
: (pauseOnBreaks == 1
|
|
? "Pause on all exceptions"
|
|
: "Pause on uncaught exceptions")
|
|
);
|
|
}
|
|
|
|
if (state !== "disconnected" || force && dbg) {
|
|
dbg.setBreakBehavior(
|
|
pauseOnBreaks === 1 ? "all" : "uncaught",
|
|
pauseOnBreaks === 0 ? false : true
|
|
);
|
|
}
|
|
|
|
pauseOnBreaks = pauseOnBreaks;
|
|
settings.set("user/debug/@pause", pauseOnBreaks);
|
|
}
|
|
|
|
function registerDebugger(type, debug) {
|
|
debuggers[type] = debug;
|
|
}
|
|
|
|
function unregisterDebugger(type, debug) {
|
|
if (debuggers[type] == debug)
|
|
delete debuggers[type];
|
|
}
|
|
|
|
function findTopFrame(frames) {
|
|
var top = frames.find(function (frame) {
|
|
return frame.istop;
|
|
});
|
|
return (top) ? top : frames[0];
|
|
}
|
|
|
|
function showDebugFrame(frame, callback) {
|
|
openFile({
|
|
scriptId: frame.sourceId,
|
|
line: frame.line - 1,
|
|
column: frame.column,
|
|
text: frame.name,
|
|
path: frame.path
|
|
}, callback);
|
|
}
|
|
|
|
function showDebugFile(script, row, column, callback) {
|
|
openFile({
|
|
scriptId: script.id,
|
|
line: row,
|
|
column: column
|
|
}, callback);
|
|
}
|
|
|
|
function openFile(options, callback) {
|
|
var row = options.line + 1;
|
|
var column = options.column;
|
|
var path = options.path;
|
|
var scriptId = options.script ? options.script.id : options.scriptId;
|
|
var source;
|
|
|
|
if (options.source)
|
|
source = options.source;
|
|
|
|
sources.every(function(src) {
|
|
if (scriptId && src.id == scriptId) {
|
|
path = src.path;
|
|
source = src;
|
|
return false;
|
|
}
|
|
if (path && src.path == path) {
|
|
scriptId = src.scriptId;
|
|
source = src;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
if (!source)
|
|
source = { id: scriptId };
|
|
|
|
var state = {
|
|
path: path,
|
|
active: true,
|
|
value: source.debug ? -1 : undefined,
|
|
document: {
|
|
title: path.substr(path.lastIndexOf("/") + 1),
|
|
meta: {
|
|
ignoreState: source.debug ? 1 : 0
|
|
},
|
|
ace: {
|
|
scriptId: scriptId,
|
|
lineoffset: 0,
|
|
customSyntax: source.customSyntax
|
|
}
|
|
}
|
|
};
|
|
if (typeof row == "number" && !isNaN(row)) {
|
|
state.document.ace.jump = {
|
|
row: row,
|
|
column: column
|
|
};
|
|
}
|
|
|
|
if (emit("beforeOpen", {
|
|
source: source,
|
|
state: state,
|
|
generated: options.generated,
|
|
callback: callback || function() {}
|
|
}) === false)
|
|
return;
|
|
|
|
tabs.open(state, function(err, tab, done) {
|
|
if (err)
|
|
return console.error(err);
|
|
|
|
tabs.focusTab(tab);
|
|
if (!done)
|
|
return;
|
|
|
|
// If we need to load the contents ourselves, lets.
|
|
dbg.getSource(source, function(err, value) {
|
|
if (err) return;
|
|
|
|
tab.document.value = value;
|
|
|
|
var jump = state.document.ace.jump;
|
|
if (tab.isActive() && jump) {
|
|
tab.document.editor
|
|
.scrollTo(jump.row, jump.column, jump.select);
|
|
}
|
|
|
|
done();
|
|
callback && callback(null, tab);
|
|
});
|
|
|
|
tab.document.getSession().readOnly = true;
|
|
});
|
|
}
|
|
|
|
function switchDebugger(runner) {
|
|
var debuggerId = runner["debugger"];
|
|
|
|
// Only update debugger implementation if switching or not yet set
|
|
if (!dbg || dbg != debuggers[debuggerId]) {
|
|
|
|
// Currently only supporting one debugger at a time
|
|
if (dbg) {
|
|
// Detach from runner
|
|
dbg.detach();
|
|
|
|
// Unload the socket
|
|
socket.unload();
|
|
|
|
// Remove all the set events
|
|
plugin.cleanUp("events", dbg);
|
|
}
|
|
|
|
// Find the new debugger
|
|
dbg = debuggers[debuggerId];
|
|
if (!dbg) {
|
|
var err = new Error(debuggerId
|
|
? "Unable to find a debugger with type " + debuggerId
|
|
: "No debugger type specified in runner");
|
|
err.code = "EDEBUGGERNOTFOUND";
|
|
return err;
|
|
}
|
|
|
|
// Attach all events necessary
|
|
initializeDebugger();
|
|
}
|
|
}
|
|
|
|
function doRun(runner, options, name, callback) {
|
|
if (options.debug)
|
|
switchDebugger(runner);
|
|
|
|
options.deferred = true;
|
|
|
|
var process = run.run(runner, options, name, function(err, pid) {
|
|
if (err) return callback(err);
|
|
|
|
if (!process || process.running < process.STARTING)
|
|
return;
|
|
|
|
var hasListeningDebugger = options.debug && (dbg && dbg.features.listeningDebugger);
|
|
|
|
if (hasListeningDebugger) {
|
|
dbg.once("connect", function() {
|
|
process.run(callback);
|
|
}, plugin);
|
|
}
|
|
else {
|
|
process.run(callback);
|
|
}
|
|
|
|
if (options.debug) {
|
|
debug(process, function(err) {
|
|
if (err) {
|
|
window.console.warn(err);
|
|
return; // Either the debugger is not found or paused
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
return process;
|
|
}
|
|
|
|
function debug(p, reconnect, callback) {
|
|
if (reconnect && process == p && dbg && dbg.connected) {
|
|
return; // We're already connecting / connected
|
|
}
|
|
|
|
process = p;
|
|
|
|
if (typeof reconnect == "function") {
|
|
callback = reconnect;
|
|
reconnect = null;
|
|
}
|
|
|
|
var runner = process.runner;
|
|
if (runner instanceof Array)
|
|
runner = runner[runner.length - 1];
|
|
|
|
// Switch to the right debugger
|
|
var err = switchDebugger(runner);
|
|
if (err) return callback(err);
|
|
|
|
if (process.running == process.STARTED)
|
|
running = process.STARTED;
|
|
else {
|
|
process.on("started", function() {
|
|
running = run.STARTED;
|
|
}, plugin);
|
|
}
|
|
|
|
if (!process.meta.$debugger) {
|
|
process.on("away", function() {
|
|
updatePanels("disable", "away");
|
|
});
|
|
|
|
process.on("back", function() {
|
|
updatePanels("enable", dbg.state);
|
|
// debug(process, true, function(){});
|
|
});
|
|
|
|
process.on("stopped", function() {
|
|
stop();
|
|
}, plugin);
|
|
|
|
process.meta.$debugger = true;
|
|
}
|
|
|
|
name = process.name;
|
|
|
|
// Hook for plugins to delay or cancel debugger attaching
|
|
// Whoever cancels is responible for calling the callback
|
|
if (emit("beforeAttach", {
|
|
process: process,
|
|
reconnect: reconnect,
|
|
runner: runner,
|
|
callback: callback
|
|
}) === false)
|
|
return;
|
|
|
|
disabledFeatures = runner.disabled || {};
|
|
|
|
// Create the socket
|
|
socket = new Socket(runner.debugport, dbg.getProxySource(process), reconnect);
|
|
|
|
if (dbg.setPathMap)
|
|
dbg.setPathMap(runner.pathMap);
|
|
// Attach the debugger to the running process
|
|
dbg.attach(socket, reconnect, callback);
|
|
}
|
|
|
|
function stop() {
|
|
if (!dbg) return;
|
|
|
|
running = run.STOPPED;
|
|
|
|
// Detach from runner
|
|
dbg && dbg.detach();
|
|
|
|
// Unload the socket
|
|
socket.unload();
|
|
|
|
updatePanels("disable", "disconnected");
|
|
|
|
if (settings.getBool("user/debug/@autoshow"))
|
|
panels.deactivate("debugger");
|
|
}
|
|
|
|
function checkAttached(callback, callbackCancel) {
|
|
if (callbackCancel == undefined)
|
|
callbackCancel = function() {};
|
|
|
|
if (state != "disconnected") {
|
|
confirm("Debugger",
|
|
"The debugger is already connected to another process.",
|
|
"Would you like to stop the current debugger process?",
|
|
function() { // Confirm
|
|
process.stop(function() {
|
|
callback();
|
|
});
|
|
},
|
|
callbackCancel, // Cancel
|
|
{ yes: "Stop current process", no: "Cancel" }
|
|
);
|
|
}
|
|
else {
|
|
callback();
|
|
}
|
|
}
|
|
|
|
/***** Lifecycle *****/
|
|
|
|
plugin.on("load", function() {
|
|
load();
|
|
});
|
|
plugin.on("draw", function(e) {
|
|
draw(e);
|
|
});
|
|
plugin.on("enable", function() {
|
|
|
|
});
|
|
plugin.on("disable", function() {
|
|
|
|
});
|
|
plugin.on("unload", function() {
|
|
loaded = false;
|
|
drawn = false;
|
|
|
|
pauseOnBreaks = null;
|
|
state = null;
|
|
sources = null;
|
|
running = null;
|
|
activeFrame = null;
|
|
dbg = null;
|
|
name = null;
|
|
process = null;
|
|
socket = null;
|
|
disabledFeatures = null;
|
|
container = null;
|
|
btnResume = null;
|
|
btnStepOver = null;
|
|
btnStepInto = null;
|
|
btnStepOut = null;
|
|
btnSuspend = null;
|
|
btnPause = null;
|
|
btnOutput = null;
|
|
btnImmediate = null;
|
|
btnSnapshots = null;
|
|
});
|
|
|
|
/***** Register and define API *****/
|
|
|
|
/**
|
|
* Generic Debugger for Cloud9. This plugin is responsible for
|
|
* binding the different debug panels to a debugger implementation.
|
|
*
|
|
* The default debug panels are:
|
|
*
|
|
* * {@link breakpoints}
|
|
* * {@link callstack}
|
|
* * {@link variables}
|
|
* * {@link watches}
|
|
*
|
|
* You can create your own debug panel using the {@link DebugPanel}
|
|
* base class.
|
|
*
|
|
* #### Remarks
|
|
*
|
|
* * The debugger also works together with the {@link immediate Immediate Panel}.
|
|
* * If you want to create a debugger for your platform, check out the
|
|
* {@link debugger.implementation} reference specification.
|
|
* * The debugger implementation is choosen based on configuration
|
|
* variables in the runner. See {@link #debug} and {@link run#run} for
|
|
* more information on runners.
|
|
*
|
|
* The following example shows how to start a debugger and
|
|
* programmatically work with breakpoints and breaks:
|
|
*
|
|
* // Start a process by executing example.js with the
|
|
* // default runner for that extension (Node.js)
|
|
* var process = run.run("auto", {
|
|
* path : "/example.js",
|
|
* debug : true
|
|
* }, function(err, pid) {
|
|
*
|
|
* // When a breakpoint is hit, ask if the user wants to break.
|
|
* debug.on("break", function(){
|
|
* if (!confirm("Would you like to break here?"))
|
|
* debug.resume();
|
|
* });
|
|
*
|
|
* // Set a breakpoint on the first line of example.js
|
|
* debug.setBreakpoint({
|
|
* path : "/example.js",
|
|
* line : 0,
|
|
* column : 0,
|
|
* enabled : true
|
|
* });
|
|
*
|
|
* // Attach a debugger to the running process
|
|
* debug.debug(process.runner, function(err) {
|
|
* if (err) throw err.message;
|
|
* });
|
|
* });
|
|
*
|
|
* @singleton
|
|
* @extends Panel
|
|
*/
|
|
plugin.freezePublicAPI({
|
|
Frame: Frame,
|
|
Source: Source,
|
|
Breakpoint: Breakpoint,
|
|
Variable: Variable,
|
|
Scope: Scope,
|
|
Data: Data,
|
|
|
|
/**
|
|
* The source of the default proxy
|
|
* @property {String} proxySource
|
|
*/
|
|
proxySource: require("text!./netproxy.js"),
|
|
|
|
/**
|
|
* When the debugger has hit a breakpoint or an exception, it breaks
|
|
* and shows the active frame in the callstack panel. The active
|
|
* frame represents the scope at which the debugger is stopped.
|
|
* @property {debugger.Frame} activeFrame
|
|
*/
|
|
get activeFrame() { return activeFrame; },
|
|
set activeFrame(frame) {
|
|
activeFrame = frame;
|
|
emit("frameActivate", { frame: frame });
|
|
},
|
|
/**
|
|
*
|
|
*/
|
|
get disabledFeatures() { return disabledFeatures || {}; },
|
|
/**
|
|
* The state of the debugger
|
|
* @property {"running"|"stopped"|"disconnected"} sources
|
|
* @readonly
|
|
*/
|
|
get state() { return state; },
|
|
/**
|
|
* A list of sources that are available from the debugger. These
|
|
* can be files that are loaded in the runtime as well as code that
|
|
* is injected by a script or by the runtime itself.
|
|
* @property {debugger.Source[]} sources
|
|
* @readonly
|
|
*/
|
|
get sources() { return sources; },
|
|
/**
|
|
* Retrieves if the debugger will break on exceptions
|
|
* @property {Boolean} breakOnExceptions
|
|
* @readonly
|
|
*/
|
|
get breakOnExceptions() { return dbg.breakOnExceptions; },
|
|
/**
|
|
* Retrieves whether the debugger will break on uncaught exceptions
|
|
* @property {Boolean} breakOnUncaughtExceptions
|
|
* @readonly
|
|
*/
|
|
get breakOnUncaughtExceptions() { return dbg.breakOnUncaughtExceptions; },
|
|
|
|
_events: [
|
|
/**
|
|
* Fires prior to a debugger attaching to a process.
|
|
*
|
|
* This event serves as a hook for plugins to delay or
|
|
* cancel a debugger attaching. Whoever cancels is responible
|
|
* for calling the callback.
|
|
*
|
|
* @event beforeAttach
|
|
* @cancellable
|
|
* @param {Object} e
|
|
* @param {Object} e.runner The object that is running the process. See {@link #debug}.
|
|
* @param {Function} e.callback The callback with which {@link #debug} was called.
|
|
*/
|
|
"beforeAttach",
|
|
/**
|
|
* Fires when the debugger has attached itself to the process.
|
|
* @event attach
|
|
* @param {Object} e
|
|
* @param {debugger.Breakpoint[]} e.breakpoints The breakpoints that are currently set.
|
|
* @param {debugger.implementation} e.implementation The used debugger implementation
|
|
*/
|
|
"attach",
|
|
/**
|
|
* Fires when the debugger has detached itself from the process.
|
|
* @event detach
|
|
*/
|
|
"detach",
|
|
/**
|
|
* Fires when the callstack frames have loaded for current
|
|
* frame that the debugger is breaked at.
|
|
* @event framesLoad
|
|
* @param {Object} e
|
|
* @param {debugger.Frame[]} e.frames The frames of the callstack.
|
|
*/
|
|
"framesLoad",
|
|
/**
|
|
* Fires when the debugger hits a breakpoint or an exception.
|
|
* @event break
|
|
* @param {Object} e
|
|
* @param {debugger.Frame} e.frame The frame where the debugger has breaked at.
|
|
* @param {debugger.Frame[]} [e.frames] The callstack frames.
|
|
* @param {Error} [e.exception] The exception that the debugger breaked at.
|
|
*/
|
|
"break",
|
|
/**
|
|
* Fires prior to opening a file from the debugger.
|
|
* @event beforeOpen
|
|
* @cancellable
|
|
* @param {Object} e
|
|
* @param {debugger.Source} e.source The source file to open.
|
|
* @param {Object} e.state The state object that is passed to the {@link tabManager#method-open} method.
|
|
* @param {Boolean} e.generated Specifies whether the file is a generated file.
|
|
*/
|
|
"beforeOpen",
|
|
/**
|
|
* Fires when a file is opened from the debugger.
|
|
* @event open
|
|
* @cancellable
|
|
* @param {Object} e
|
|
* @param {debugger.Source} e.source The source file to open.
|
|
* @param {String} e.path The path of the source file to open
|
|
* @param {String} e.value The value of the source file.
|
|
* @param {Function} e.done Call this function if you are cancelling the event.
|
|
* @param {Function} e.done.value The value of the source file
|
|
* @param {Tab} e.tab The created tab for the source file.
|
|
*/
|
|
"open",
|
|
/**
|
|
* Fires when the panels are being drawn.
|
|
* @event drawPanels
|
|
* @param {Object} e
|
|
* @param {HTMLElement} e.html The html container for the panel.
|
|
* @param {AMLElement} e.aml The aml container for the panel.
|
|
* @private
|
|
*/
|
|
"drawPanels",
|
|
/**
|
|
* Fires when the state of the debugger changes.
|
|
* @event stateChange
|
|
* @param {Object} e
|
|
* @param {"disconnected"|"running"|"stopped"} e.state The state of the debugger.
|
|
* <table>
|
|
* <tr><td>Value</td><td> Description</td></tr>
|
|
* <tr><td>"disconnected"</td><td> Not connected to a process</td></tr>
|
|
* <tr><td>"stopped"</td><td> paused on breakpoint</td></tr>
|
|
* <tr><td>"running"</td><td> process is running</td></tr>
|
|
* </table>
|
|
*/
|
|
"stateChange",
|
|
/**
|
|
* Fires when the active frame changes. See also {@link #activeFrame}.
|
|
* @event frameActivate
|
|
* @param {Object} e
|
|
* @param {debugger.Frame} e.frame The frame that is currently active.
|
|
*/
|
|
"frameActivate",
|
|
/**
|
|
* Fires when a new list of sources comes in from the debugger.
|
|
* @event sources
|
|
* @param {Object} e
|
|
* @param {debugger.Source[]} e.sources The list of sources
|
|
*/
|
|
"sources",
|
|
/**
|
|
* Fires when a new source file is compiled.
|
|
* @event sourcesCompile
|
|
* @param {Object} e
|
|
* @param {debugger.Source} e.source The compiled source file.
|
|
*/
|
|
"sourcesCompile",
|
|
/**
|
|
* Fires when a breakpoint is updated (for instance with location info).
|
|
* @event breakpointUpdate
|
|
* @param {Object} e
|
|
* @param {debugger.Breakpoint} e.breakpoint The breakpoint that is updated.
|
|
*/
|
|
"breakpointUpdate",
|
|
/**
|
|
* Fires when the debugger needs a list of breakpoints.
|
|
* @event getBreakpoints
|
|
* @private
|
|
*/
|
|
"getBreakpoints"
|
|
],
|
|
|
|
/**
|
|
*
|
|
*/
|
|
run: doRun,
|
|
|
|
/**
|
|
* Attaches the debugger that is specified by the runner to the
|
|
* running process that is started using the same runner.
|
|
*
|
|
* *N.B.: There can only be one debugger attached at the same time.*
|
|
*
|
|
* @param {run.Process} process The process that will be debugger.
|
|
* @param {Boolean} [reconnect] Specifies whether the debugger should reconnect to an existing debug session.
|
|
* @param {Function} callback Called when the debugger is attached.
|
|
* @param {Error} callback.err Error object with information on an error if one occured.
|
|
*/
|
|
debug: debug,
|
|
|
|
/**
|
|
* Detaches the started debugger from it's process.
|
|
*/
|
|
stop: stop,
|
|
|
|
/**
|
|
* Registers a {@link debugger.implementation debugger implementation}
|
|
* with a unique name. This name is used as the "debugger" property
|
|
* of the runner.
|
|
* @param {String} name The unique name of this debugger implementation.
|
|
* @param {debugger.implementation} debugger The debugger implementation.
|
|
*/
|
|
registerDebugger: registerDebugger,
|
|
|
|
/**
|
|
* Unregisters a {@link debugger.implementation debugger implementation}.
|
|
* @param {String} name The unique name of this debugger implementation.
|
|
* @param {debugger.implementation} debugger The debugger implementation.
|
|
*/
|
|
unregisterDebugger: unregisterDebugger,
|
|
|
|
/**
|
|
* Continues execution of a process after it has hit a breakpoint.
|
|
*/
|
|
resume: function() { dbg.resume(); },
|
|
|
|
/**
|
|
* Pauses the execution of a process at the next statement.
|
|
*/
|
|
suspend: function() { dbg.suspend(); },
|
|
|
|
/**
|
|
* Step into the next statement.
|
|
*/
|
|
stepInto: function() { dbg.stepInto(); },
|
|
|
|
/**
|
|
* Step out of the current statement.
|
|
*/
|
|
stepOut: function() { dbg.stepOut(); },
|
|
|
|
/**
|
|
* Step over the next statement.
|
|
*/
|
|
stepOver: function() { dbg.stepOver(); },
|
|
|
|
/**
|
|
* Retrieves the contents of a source file from the debugger (not
|
|
* the file system).
|
|
* @param {debugger.Source} source The source file.
|
|
* @param {Function} callback Called when the contents is retrieved.
|
|
* @param {Function} callback.err Error object if an error occured.
|
|
* @param {Function} callback.data The contents of the file.
|
|
*/
|
|
getSource: function(source, callback) {
|
|
dbg.getSource(source, callback);
|
|
},
|
|
|
|
/**
|
|
* Defines how the debugger deals with exceptions.
|
|
* @param {"all"/"uncaught"} type Specifies which errors to break on.
|
|
* @param {Boolean} enabled Specifies whether to enable breaking on exceptions.
|
|
* @param {Function} callback Called after the setting is changed.
|
|
* @param {Error} callback.err The error if any error occured.
|
|
*/
|
|
setBreakBehavior: function(type, enabled, callback) {
|
|
// dbg.setBreakBehavior(type, enabled, callback);
|
|
togglePause(enabled ? (type == "uncaught" ? 1 : 2) : 0);
|
|
},
|
|
|
|
/**
|
|
* Evaluates an expression in a frame or in global space.
|
|
* @param {String} expression The expression.
|
|
* @param {debugger.Frame} frame The stack frame which serves as the context of the expression.
|
|
* @param {Boolean} global Specifies whether to execute the expression in global space.
|
|
* @param {Boolean} disableBreak Specifies whether to disabled breaking when executing this expression.
|
|
* @param {Function} callback Called after the expression has executed.
|
|
* @param {Error} callback.err The error if any error occured.
|
|
* @param {debugger.Variable} callback.variable The result of the expression.
|
|
*/
|
|
evaluate: function(expression, frame, global, disableBreak, callback) {
|
|
dbg.evaluate(expression, frame, global, disableBreak, callback);
|
|
},
|
|
|
|
/**
|
|
* Check whether a debugger is already attached. If the debugger is
|
|
* already attached it will present a dialog to the user asking
|
|
* how to handle the situation.
|
|
* @param {Function} callback Called when the user chooses to run
|
|
* the new process.
|
|
*/
|
|
checkAttached: checkAttached,
|
|
|
|
/**
|
|
* Returns the topmost frame from a set of frames
|
|
* @param {debugger.Frame[]} frames The stack of frames
|
|
*/
|
|
findTopFrame: findTopFrame,
|
|
|
|
/**
|
|
* Displays a frame in the ace editor.
|
|
* @param {debugger.Frame} frame The frame to display
|
|
*/
|
|
showDebugFrame: showDebugFrame,
|
|
|
|
/**
|
|
* Displays a debugger source file in the ace editor
|
|
* @param {debugger.Source} script The source file to display
|
|
* @param {Number} row The row (zero bound) to scroll to.
|
|
* @param {Number} column The column (zero bound) to scroll to.
|
|
*/
|
|
showDebugFile: showDebugFile,
|
|
|
|
/**
|
|
* Opens a file from disk or from the debugger.
|
|
* @param {Number} [row] The row (zero bound) to scroll to.
|
|
* @param {Number} [column] The column (zero bound) to scroll to.
|
|
* @param {String} [path] The path of the file to open
|
|
* @param {debugger.Source} [script] The source file to open
|
|
* @param {String} [scriptId] The script id of the file to open
|
|
* @param {Boolean} [generated]
|
|
*/
|
|
openFile: openFile
|
|
});
|
|
|
|
register(null, {
|
|
"debugger": plugin
|
|
});
|
|
}
|
|
}); |