kopia lustrzana https://github.com/c9/core
1143 wiersze
39 KiB
JavaScript
1143 wiersze
39 KiB
JavaScript
define(function(require, exports, module) {
|
|
main.consumes = [
|
|
"DebugPanel", "settings", "ui", "tabManager", "debugger", "ace",
|
|
"MenuItem", "Divider", "save", "layout", "fs", "c9.analytics"
|
|
];
|
|
main.provides = ["breakpoints"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var DebugPanel = imports.DebugPanel;
|
|
var settings = imports.settings;
|
|
var save = imports.save;
|
|
var ui = imports.ui;
|
|
var tabs = imports.tabManager;
|
|
var debug = imports.debugger;
|
|
var layout = imports.layout;
|
|
var aceHandle = imports.ace;
|
|
var MenuItem = imports.MenuItem;
|
|
var Divider = imports.Divider;
|
|
var fs = imports.fs;
|
|
var analytics = imports["c9.analytics"];
|
|
|
|
var Breakpoint = require("./data/breakpoint");
|
|
var basename = require("path").basename;
|
|
var Tree = require("ace_tree/tree");
|
|
var TreeData = require("ace_tree/data_provider");
|
|
var escapeHTML = require("ace/lib/lang").escapeHTML;
|
|
|
|
/***** Initialization *****/
|
|
|
|
var plugin = new DebugPanel("Ajax.org", main.consumes, {
|
|
caption: "Breakpoints",
|
|
index: 400
|
|
});
|
|
// var emit = plugin.getEmitter();
|
|
|
|
var changed = false;
|
|
var breakpoints = [];
|
|
var enableBreakpoints = true;
|
|
|
|
var dbg;
|
|
var list, listEl, menu, model, hCondition, hInput; // UI Elements
|
|
var btnBreakpoints, btnBpRemove, codebox;
|
|
var conditionBreakpoint;
|
|
|
|
var loaded = false;
|
|
function load() {
|
|
if (loaded) return false;
|
|
loaded = true;
|
|
|
|
model = new TreeData();
|
|
model.$sortNodes = false;
|
|
model.renderRow = function(row, html, config) {
|
|
var bp = this.visibleItems[row];
|
|
html.push('<div class="', this.getClassName(bp), '">',
|
|
'<span class="checkbox"> </span>',
|
|
'<div class="content">',
|
|
escapeHTML(bp.text || "") + ":", bp.line + 1,
|
|
'<div>',
|
|
escapeHTML(bp.content || "") + " ",
|
|
'</div></div>',
|
|
'<strong class="btnclose"> </strong>',
|
|
'</div>');
|
|
};
|
|
model.getEmptyMessage = function() {
|
|
return "No breakpoints";
|
|
};
|
|
|
|
model.getClassName = function(node) {
|
|
return "bpItem " + (node.enabled ? "checked " : " ") + node.className;
|
|
};
|
|
|
|
fs.on("afterRename", function(e) {
|
|
var oldPath = e.args[0];
|
|
var newPath = e.args[1];
|
|
|
|
var changed = false;
|
|
breakpoints.forEach(function(bp) {
|
|
if (bp.path && bp.path.indexOf(oldPath) === 0) {
|
|
var char = bp.path.charAt(oldPath.length);
|
|
// Make sure that a path like /Untitled1 is not matched
|
|
// by a path like /Untitled, which are clearly different
|
|
// files with no relation to each other
|
|
if (!char || char == "/") {
|
|
bp.path = bp.path.replace(oldPath, newPath);
|
|
bp.text = basename(bp.path);
|
|
changed = true;
|
|
updateBreakpointAtDebugger(bp, "add");
|
|
}
|
|
}
|
|
});
|
|
if (changed)
|
|
settings.save();
|
|
});
|
|
|
|
tabs.on("tabAfterActivate", function(e) {
|
|
var tab = e.tab;
|
|
if (!tab || !tab.editor || tab.editor.type != "ace")
|
|
return;
|
|
|
|
var ace = tab.editor.ace;
|
|
|
|
decorateAce(ace);
|
|
updateDocument(tab.document);
|
|
decorateDocument(tab.document);
|
|
});
|
|
|
|
aceHandle.on("create", function(e) {
|
|
e.editor.on("createAce", decorateAce, plugin);
|
|
}, plugin);
|
|
|
|
save.on("afterSave", function(e) {
|
|
var doc = e.document;
|
|
if (dbg && dbg.features.liveUpdate) {
|
|
dbg.on("setScriptSource", function() {
|
|
updateMovedBreakpoints(doc);
|
|
});
|
|
}
|
|
else {
|
|
updateMovedBreakpoints(doc);
|
|
}
|
|
});
|
|
|
|
debug.on("attach", function(e) {
|
|
dbg = e.implementation;
|
|
|
|
// Add breakpoints that we potentially got from the server
|
|
e.breakpoints.forEach(function(bp) {
|
|
if (bp.serverOnly)
|
|
setBreakpoint(bp, true);
|
|
});
|
|
|
|
// Deactivate breakpoints if user wants to
|
|
if (!enableBreakpoints)
|
|
deactivateAll(true);
|
|
});
|
|
debug.on("detach", function(e) {
|
|
dbg = null;
|
|
});
|
|
debug.on("stateChange", function(e) {
|
|
plugin[e.action]();
|
|
});
|
|
|
|
debug.on("getBreakpoints", function() {
|
|
return breakpoints;
|
|
});
|
|
|
|
debug.on("breakpointUpdate", function(e) {
|
|
var bp = e.breakpoint;
|
|
|
|
if (bp.hidden)
|
|
return;
|
|
|
|
if (bp.actual) {
|
|
// Delete breakpoints that are outside of the doc length
|
|
var tab = tabs.findTab(bp.path);
|
|
if (tab) {
|
|
var session = tab.document.getSession();
|
|
if (session && session.session) {
|
|
var len = session.session.getLength();
|
|
|
|
if (bp.actual.line == len) {
|
|
bp.actual.line = len - 1;
|
|
}
|
|
else if (bp.actual.line > len) {
|
|
clearBreakpoint(bp);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var loc = bp.actual || bp;
|
|
var bps = findBreakpoints(bp.path, loc.line);
|
|
|
|
if (bps.length > 1) {
|
|
for (var bpi, i = 0, l = bps.length; i < l; i++) {
|
|
bpi = bps[i];
|
|
if (bpi == bp) continue;
|
|
|
|
if (bpi.actual && bpi.actual.line != bpi.line) {
|
|
bpi.invalid = true;
|
|
}
|
|
else {
|
|
bp.invalid = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
bp.invalid = false;
|
|
}
|
|
|
|
redrawBreakpoint(bp);
|
|
}, plugin);
|
|
|
|
// Breakpoints may have already been set
|
|
breakpoints.forEach(function(bp) {
|
|
updateBreakpointAtDebugger(bp, "add");
|
|
});
|
|
|
|
// restore the breakpoints from the IDE settings
|
|
settings.on("read", function (e) {
|
|
settings.setDefaults("user/breakpoints", [
|
|
["active", true]
|
|
]);
|
|
|
|
var bps = settings.getJson("state/breakpoints");
|
|
|
|
// bind it to the Breakpoint model
|
|
breakpoints = (bps || []).map(function(bp) {
|
|
return new Breakpoint(bp);
|
|
});
|
|
model.setRoot(breakpoints);
|
|
model.visibleItems = breakpoints;
|
|
|
|
// update the currently active document
|
|
if (tabs.focussedTab && tabs.focussedTab.editor.type == "ace") {
|
|
updateDocument(tabs.focussedTab.document);
|
|
}
|
|
|
|
enableBreakpoints = settings.getBool("user/breakpoints/@active");
|
|
toggleBreakpoints(enableBreakpoints);
|
|
|
|
if (!enableBreakpoints && drawn)
|
|
list.renderer.setStyle("listBPDisabled");
|
|
});
|
|
|
|
settings.on("write", function (e) {
|
|
if (changed) {
|
|
var list = breakpoints.map(function(bp) {
|
|
return bp.json;
|
|
});
|
|
settings.setJson("state/breakpoints", list);
|
|
}
|
|
|
|
changed = false;
|
|
});
|
|
|
|
// Wait for the gutter menu
|
|
aceHandle.on("draw", function() {
|
|
|
|
// We need the gutter context menu
|
|
var menu = aceHandle.gutterContextMenu;
|
|
var meta = menu.meta;
|
|
|
|
menu.append(new MenuItem({
|
|
caption: "Continue to Here",
|
|
position: 100,
|
|
isAvailable: function() {
|
|
return dbg && dbg.state == "stopped"
|
|
&& meta.className.indexOf("breakpoint") === -1;
|
|
},
|
|
onclick: function() {
|
|
var path = meta.ace.session.c9doc.tab.path;
|
|
if (!path)
|
|
return;
|
|
// Add hidden breakpoint
|
|
var breakpoint = new Breakpoint({
|
|
path: path,
|
|
line: meta.line,
|
|
column: 0,
|
|
hidden: true,
|
|
enabled: true
|
|
});
|
|
|
|
// Set breakpoint
|
|
dbg.setBreakpoint(breakpoint, function(err, bp) {
|
|
if (err || bp.actual && bp.actual.line != bp.line) {
|
|
updateBreakpointAtDebugger(breakpoint, "remove");
|
|
return; // Won't do this if bp can't be set
|
|
}
|
|
|
|
debug.on("break", done);
|
|
debug.on("detach", done);
|
|
|
|
// Deactivate all breakpoints
|
|
deactivateAll(true);
|
|
|
|
// Continue
|
|
debug.resume();
|
|
});
|
|
|
|
// Wait until break
|
|
function done() {
|
|
// Remove breakpoint
|
|
updateBreakpointAtDebugger(breakpoint, "remove");
|
|
|
|
// Re-activate all breakpoints
|
|
activateAll(true);
|
|
|
|
debug.off("break", done);
|
|
debug.off("detach", done);
|
|
}
|
|
}
|
|
}, plugin));
|
|
|
|
menu.append(new Divider({ position: 150 }, plugin));
|
|
|
|
var itemAdd = menu.append(new MenuItem({
|
|
position: 200,
|
|
isAvailable: function() {
|
|
itemAdd.caption = meta.className.indexOf("breakpoint") > -1
|
|
? "Remove Breakpoint"
|
|
: "Add Breakpoint";
|
|
return true;
|
|
},
|
|
onclick: function() {
|
|
editBreakpoint(meta.className.indexOf("breakpoint") > -1
|
|
? "remove" : "add", meta.ace, meta.line);
|
|
}
|
|
}, plugin));
|
|
|
|
var itemCondition = menu.append(new MenuItem({
|
|
position: 300,
|
|
isAvailable: function() {
|
|
var name = meta.className;
|
|
itemCondition.caption = name.indexOf("condition") > -1
|
|
? "Edit Condition"
|
|
: (name.indexOf("breakpoint") > -1
|
|
? "Set Condition"
|
|
: "Add Conditional Breakpoint");
|
|
return !dbg || dbg.features.conditionalBreakpoints;
|
|
},
|
|
onclick: function() {
|
|
editBreakpoint("edit", meta.ace, meta.line);
|
|
}
|
|
}, plugin));
|
|
});
|
|
}
|
|
|
|
var drawn;
|
|
function draw(options) {
|
|
if (drawn) return false;
|
|
drawn = true;
|
|
|
|
// Create UI elements
|
|
var markup = require("text!./breakpoints.xml");
|
|
ui.insertMarkup(options.aml, markup, plugin);
|
|
|
|
listEl = plugin.getElement("list");
|
|
list = new Tree(listEl.$ext);
|
|
list.setTheme({ cssClass: "blackdg" });
|
|
list.setOption("maxLines", 200);
|
|
|
|
layout.on("eachTheme", function(e) {
|
|
var height = parseInt(ui.getStyleRule(".listBP .bpItem", "height"), 10) || 52;
|
|
// model.rowHeightInner = height - 1;
|
|
model.rowHeight = height;
|
|
|
|
if (e.changed) (list).resize(true);
|
|
});
|
|
|
|
list.setDataProvider(model);
|
|
|
|
list.on("click", function(e) {
|
|
var bp = e.getNode();
|
|
if (!bp || e.getButton())
|
|
return;
|
|
|
|
var className = e.domEvent.target.className || "";
|
|
if (className.indexOf("btnclose") != -1)
|
|
clearBreakpoint(bp);
|
|
else if (className.indexOf("checkbox") != -1)
|
|
list._signal("afterCheck", { node: bp });
|
|
else
|
|
gotoBreakpoint(bp);
|
|
});
|
|
|
|
// Breakpoint is removed
|
|
list.on("delete", function(e) {
|
|
var bp = findBreakpoint(e.node);
|
|
clearBreakpoint(bp);
|
|
});
|
|
|
|
// Breakpoint is enabled / disabled
|
|
list.on("afterCheck", function(e) {
|
|
var bp = findBreakpoint(e.node);
|
|
|
|
if (!bp.enabled)
|
|
enableBreakpoint(bp);
|
|
else
|
|
disableBreakpoint(bp);
|
|
});
|
|
|
|
menu = plugin.getElement("menu");
|
|
|
|
listEl.setAttribute("contextmenu", menu);
|
|
|
|
if (!enableBreakpoints)
|
|
list.renderer.setStyle("listBPDisabled");
|
|
|
|
menu.on("prop.visible", function() {
|
|
var length = model.visibleItems.length;
|
|
|
|
menu.childNodes.forEach(function(item) {
|
|
if (item.localName == "divider") return;
|
|
if (item.value == "deactivate") {
|
|
item.setAttribute("caption", enableBreakpoints
|
|
? "Deactivate Breakpoints"
|
|
: "Activate Breakpoints");
|
|
return;
|
|
}
|
|
|
|
item.setAttribute("disabled", length ? false : true);
|
|
});
|
|
});
|
|
|
|
menu.on("itemclick", function(e) {
|
|
var bp = list.selection.getCursor();
|
|
if (!bp)
|
|
return;
|
|
|
|
if (e.value == "remove") {
|
|
clearBreakpoint(findBreakpoint(bp));
|
|
}
|
|
else if (e.value == "remove-all") {
|
|
for (var i = breakpoints.length - 1; i >= 0; i--) {
|
|
clearBreakpoint(breakpoints[i]);
|
|
}
|
|
}
|
|
else if (e.value == "deactivate") {
|
|
if (enableBreakpoints)
|
|
deactivateAll();
|
|
else
|
|
activateAll();
|
|
}
|
|
else if (e.value == "enable-all") {
|
|
breakpoints.forEach(function(bp) {
|
|
enableBreakpoint(bp);
|
|
});
|
|
}
|
|
else if (e.value == "disable-all") {
|
|
breakpoints.forEach(function(bp) {
|
|
disableBreakpoint(bp);
|
|
});
|
|
}
|
|
});
|
|
|
|
var hbox1 = debug.getElement("hbox");
|
|
var hbox2 = debug.getElement("hbox2");
|
|
btnBreakpoints = hbox1.insertBefore(new ui.button({
|
|
id: "btnBreakpoints",
|
|
tooltip: "Deactivate All Breakpoints",
|
|
icon: true,
|
|
skinset: "default",
|
|
skin: "c9-menu-btn",
|
|
class: "nosize toggle_breakpoints2"
|
|
}), hbox1.selectSingleNode("a:divider").nextSibling);
|
|
btnBpRemove = hbox2.insertBefore(new ui.button({
|
|
id: "btnBpRemove",
|
|
tooltip: "Clear All Breakpoints",
|
|
icon: true,
|
|
skinset: "default",
|
|
skin: "c9-menu-btn",
|
|
class: "nosize remove_breakpoints"
|
|
}), hbox2.selectSingleNode("a:divider"));
|
|
plugin.addElement(btnBreakpoints, btnBpRemove);
|
|
|
|
if (!enableBreakpoints)
|
|
toggleBreakpoints(enableBreakpoints);
|
|
|
|
btnBreakpoints.on("click", function() {
|
|
toggleBreakpoints();
|
|
});
|
|
|
|
btnBpRemove.on("click", function() {
|
|
for (var i = breakpoints.length - 1; i >= 0; i--) {
|
|
clearBreakpoint(breakpoints[i]);
|
|
}
|
|
});
|
|
}
|
|
|
|
var drawnCondition;
|
|
function drawCondition() {
|
|
if (drawnCondition) return;
|
|
drawnCondition = true;
|
|
|
|
// Create HTML elements
|
|
var html = require("text!./breakpoints.html");
|
|
hCondition = ui.insertHtml(null, html, plugin)[0];
|
|
|
|
hInput = hCondition.querySelector(".input");
|
|
codebox = new apf.codebox({
|
|
skin: "simplebox",
|
|
"class": "dark",
|
|
focusselect: true,
|
|
htmlNode: hInput,
|
|
"initial-message": "Your Expression"
|
|
});
|
|
|
|
codebox.ace.commands.addCommands([
|
|
{
|
|
bindKey: "ESC",
|
|
exec: function() { hCondition.style.display = "none"; }
|
|
}, {
|
|
name: "confirmCondition",
|
|
bindKey: "Enter",
|
|
exec: function() {
|
|
setCondition(conditionBreakpoint, codebox.getValue());
|
|
hCondition.style.display = "none";
|
|
}
|
|
},
|
|
]);
|
|
|
|
apf.addEventListener("movefocus", function(e) {
|
|
if (e.toElement != codebox)
|
|
codebox.execCommand("confirmCondition");
|
|
});
|
|
}
|
|
|
|
/***** Helper Functions *****/
|
|
|
|
function toggleBreakpoints(force) {
|
|
var enable = force !== undefined
|
|
? force
|
|
: !enableBreakpoints;
|
|
|
|
if (btnBreakpoints) {
|
|
btnBreakpoints.setAttribute("class", "nosize " + (enableBreakpoints
|
|
? "toggle_breakpoints2"
|
|
: "toggle_breakpoints1"));
|
|
btnBreakpoints.setAttribute("tooltip",
|
|
enableBreakpoints
|
|
? "Deactivate All Breakpoints"
|
|
: "Activate All Breakpoints"
|
|
);
|
|
}
|
|
|
|
if (enable)
|
|
activateAll();
|
|
else
|
|
deactivateAll();
|
|
|
|
tabs.getPanes().forEach(function(pane) {
|
|
var tab = pane.getTab();
|
|
if (tab && tab.editorType == "ace")
|
|
updateDocument(tab.document);
|
|
});
|
|
}
|
|
|
|
// Breakpoints
|
|
function updateBreakpointAtDebugger(bp, action) {
|
|
// Give plugins the ability to update a breakpoint before
|
|
// setting it in the debugger
|
|
// emit("breakpointsUpdate", e);
|
|
|
|
if (!debug.state || debug.state == "disconnected")
|
|
return;
|
|
|
|
// There used to be a timeout here.
|
|
if (!dbg)
|
|
return setTimeout(function() {
|
|
updateBreakpointAtDebugger(bp, action);
|
|
}, 500);
|
|
|
|
if (action == "enable" || action == "disable") {
|
|
if (enableBreakpoints)
|
|
dbg.changeBreakpoint(bp);
|
|
}
|
|
else if (action == "condition" || action == "ignoreCount") {
|
|
dbg.changeBreakpoint(bp);
|
|
}
|
|
else if (action == "add") {
|
|
dbg.setBreakpoint(bp);
|
|
}
|
|
else if (action == "remove") {
|
|
dbg.clearBreakpoint(bp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds and event listener to this ace instance that draws breakpoints
|
|
*/
|
|
function decorateAce(editor) {
|
|
if (editor.$breakpointListener)
|
|
return;
|
|
|
|
var el = document.createElement("div");
|
|
editor.renderer.$gutter.appendChild(el);
|
|
el.style.cssText = "position:absolute;top:0;bottom:0;left:0;width:18px;cursor:pointer";
|
|
|
|
editor.on("guttermousedown", editor.$breakpointListener = function(e) {
|
|
if (e.getButton()) // !editor.isFocused()
|
|
return;
|
|
|
|
var gutterRegion = editor.renderer.$gutterLayer.getRegion(e);
|
|
if (gutterRegion != "markers")
|
|
return;
|
|
|
|
e.stop();
|
|
|
|
var line = e.getDocumentPosition().row;
|
|
// var className = editor.session.getBreakpoints()[line];
|
|
var action;
|
|
|
|
var bp = findBreakpoint(e.editor.session.c9doc.tab.path, line);
|
|
|
|
// Show condition dialog
|
|
if (e.getAccelKey()) {
|
|
action = "edit";
|
|
}
|
|
// Toggle disabled/enabled
|
|
else if (e.getShiftKey()) {
|
|
action = !bp || !bp.enabled//className && className.indexOf("disabled") > -1
|
|
? "enable" : "disable";
|
|
}
|
|
// Toggle add/remove
|
|
else {
|
|
action = !bp ? "create" :
|
|
(!bp.enabled ? "enable" : "remove");
|
|
}
|
|
|
|
editBreakpoint(action, editor, line);
|
|
});
|
|
}
|
|
|
|
function editBreakpoint(action, editor, line) {
|
|
var session = editor.session;
|
|
var path = session.c9doc.tab.path;
|
|
var removed = false;
|
|
var enabled = true;
|
|
|
|
var obp = findBreakpoint(path, line, true).filter(function(b) {
|
|
return b.invalid && b.line != line ? false : true;
|
|
})[0];
|
|
|
|
function createBreakpoint(condition) {
|
|
var caption = basename(path);
|
|
var lineContents = session.getLine(line);
|
|
|
|
return setBreakpoint({
|
|
path: path,
|
|
line: line,
|
|
column: (lineContents.match(/^(\s+)/) || [0, ""])[1].length,
|
|
text: caption,
|
|
content: lineContents.slice(0, 200),
|
|
time: Date.now(),
|
|
enabled: enabled,
|
|
condition: condition
|
|
});
|
|
}
|
|
|
|
// Show condition dialog
|
|
if (action == "edit") {
|
|
if (!enableBreakpoints)
|
|
activateAll();
|
|
showConditionDialog(editor, createBreakpoint, path, line, obp);
|
|
return;
|
|
}
|
|
// Toggle disabled/enabled
|
|
else if (action == "enable" || action == "disable") {
|
|
enabled = action == "enable";
|
|
removed = false;
|
|
}
|
|
// Create
|
|
else if (action == "create") {
|
|
var mode = session.syntax;
|
|
if (mode === "php")
|
|
analytics.track("Breakpoint Created: " + mode);
|
|
if (!enableBreakpoints)
|
|
activateAll();
|
|
}
|
|
// Toggle add/remove
|
|
else {
|
|
removed = action == "remove";
|
|
enabled = true;
|
|
}
|
|
|
|
// Remove old breakpoint
|
|
if (obp) {
|
|
if (removed)
|
|
clearBreakpoint(obp);
|
|
else if (enabled)
|
|
enableBreakpoint(obp);
|
|
else
|
|
disableBreakpoint(obp);
|
|
return;
|
|
}
|
|
|
|
createBreakpoint();
|
|
}
|
|
|
|
function showConditionDialog(ace, createBreakpoint, path, line, breakpoint) {
|
|
if (!breakpoint)
|
|
breakpoint = createBreakpoint();
|
|
|
|
drawCondition();
|
|
|
|
// Attach dialog to ace
|
|
ace.container.parentNode.appendChild(hCondition);
|
|
hCondition.style.display = "block";
|
|
|
|
// Set left
|
|
// var gutterWidth = ace.renderer.$gutterLayer.gutterWidth;
|
|
hCondition.style.left = "2px"; //(gutterWidth + 5) + "px"; //gutter width
|
|
|
|
// Set top
|
|
var pos = ace.renderer.$cursorLayer.getPixelPosition({
|
|
row: line + 1,
|
|
column: 0
|
|
}, true);
|
|
hCondition.style.top = (pos.top + 3) + "px"; // line position
|
|
|
|
// Set current value
|
|
codebox.setValue(breakpoint.condition || "");
|
|
|
|
var node = hCondition.getElementsByTagName("div")[0].firstChild;
|
|
node.nodeValue = node.nodeValue.replace(/\d+/, line + 1);
|
|
|
|
conditionBreakpoint = breakpoint;
|
|
|
|
setTimeout(function() { codebox.focus(); });
|
|
}
|
|
|
|
/**
|
|
* Adds and event listener to an ace session that updates breakpoints
|
|
*/
|
|
function decorateDocument(doc) {
|
|
var session = doc.getSession();
|
|
if (session.hasBreakpoints)
|
|
return;
|
|
|
|
var aceSession = session.session;
|
|
// A file was loaded that doesn't exists and is already destroyed
|
|
if (!aceSession)
|
|
return;
|
|
|
|
aceSession.on("change", function(delta) {
|
|
var breakpoints = aceSession.$breakpoints;
|
|
var doc = aceSession.c9doc;
|
|
|
|
if (!breakpoints.length || !doc.tab.loaded || !doc.hasValue())
|
|
return;
|
|
|
|
var bpsInDoc = findBreakpoints(doc.tab.path);
|
|
if (!bpsInDoc.length)
|
|
return;
|
|
|
|
if (delta.end.row == delta.start.row)
|
|
return;
|
|
|
|
var len, firstRow;
|
|
len = delta.end.row - delta.start.row;
|
|
if (delta.action == "insert") {
|
|
firstRow = delta.start.column
|
|
? delta.start.row + 1
|
|
: delta.start.row;
|
|
}
|
|
else {
|
|
firstRow = delta.start.row;
|
|
}
|
|
|
|
var i;
|
|
var lines = [];
|
|
|
|
bpsInDoc.forEach(function(bp) {
|
|
if (bp.moved == -1)
|
|
return clearBreakpoint(bp);
|
|
|
|
var line;
|
|
!isNaN(line = bp.moved)
|
|
|| !isNaN(line = (bp.actual || 0).line)
|
|
|| !isNaN(line = (bp.sourcemap || 0).line)
|
|
|| (line = bp.line);
|
|
|
|
if (typeof line !== "number" && isNaN(line))
|
|
return console.warn("Could not find breakpoint, file likely has unsaved changes");
|
|
|
|
lines[line] = bp;
|
|
});
|
|
|
|
if (delta.action[0] == "i") {
|
|
var args = Array(len);
|
|
args.unshift(firstRow, 0);
|
|
breakpoints.splice.apply(breakpoints, args);
|
|
|
|
// Insert should move breakpoints out of the way
|
|
for (i = firstRow; i < lines.length; i++) {
|
|
if (lines[i]) {
|
|
changed = true;
|
|
lines[i].moved = i + len;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
var rem = breakpoints.splice(firstRow + 1, len);
|
|
|
|
// Remove deletes breakpoints
|
|
var max = firstRow + len + 1;
|
|
for (i = firstRow; i < max; i++) {
|
|
if (lines[i]) {
|
|
changed = true;
|
|
lines[i].moved = -1;
|
|
}
|
|
}
|
|
|
|
if (!breakpoints[firstRow]) {
|
|
for (i = rem.length; i--;) {
|
|
if (rem[i]) {
|
|
changed = true;
|
|
if (!lines[firstRow + i + 1]) {
|
|
console.warn("Could not find the breakpoint");
|
|
continue;
|
|
}
|
|
breakpoints[firstRow] = rem[i];
|
|
lines[firstRow + i + 1].moved = firstRow;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (lines[firstRow]) {
|
|
lines[firstRow].moved = firstRow;
|
|
}
|
|
|
|
// Move other breakpoints
|
|
for (i = max; i < lines.length; i++) {
|
|
if (lines[i]) {
|
|
changed = true;
|
|
lines[i].moved = i - len;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (changed)
|
|
settings.save();
|
|
});
|
|
|
|
session.hasBreakpoints = true;
|
|
}
|
|
|
|
function updateDocument(doc) {
|
|
if (!doc.editor || doc.editor.type != "ace")
|
|
return;
|
|
|
|
var session = doc.getSession();
|
|
var rows = [];
|
|
var path = doc.tab.path;
|
|
|
|
if (!session.session)
|
|
return;
|
|
|
|
breakpoints.forEach(function(bp) {
|
|
if (bp.path != path || bp.sourcemap && bp.sourcemap.path != path || bp.moved == -1)
|
|
return;
|
|
|
|
var loc = bp.invalid ? bp : (bp.actual || bp.sourcemap || bp);
|
|
var line; !isNaN(line = bp.moved) || (line = loc.line);
|
|
rows[line]
|
|
= " ace_breakpoint"
|
|
+ (bp.condition ? " condition" : "")
|
|
+ (bp.enabled && enableBreakpoints ? "" : " disabled ")
|
|
+ (bp.invalid ? " invalid" : "");
|
|
});
|
|
|
|
session.session.$breakpoints = rows;
|
|
session.session._emit("changeBreakpoint", {});
|
|
}
|
|
|
|
function updateMovedBreakpoints(doc) {
|
|
var bpsInDoc = findBreakpoints(doc.tab.path);
|
|
bpsInDoc.forEach(function(bp) {
|
|
if (typeof bp.moved == "number" && !isNaN(bp.moved)) {
|
|
if (bp.moved == -1)
|
|
clearBreakpoint(bp);
|
|
else if (bp.moved != bp.line) {
|
|
clearBreakpoint(bp);
|
|
bp.line = bp.moved;
|
|
bp.actual = undefined;
|
|
setBreakpoint(bp);
|
|
}
|
|
bp.moved = undefined;
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateBreakpoint(breakpoint, action, force) {
|
|
//This can be optimized, currently rereading everything
|
|
var tab = tabs.findTab(breakpoint.path);
|
|
if (tab) {
|
|
// @todo there used to be a timeout here
|
|
updateDocument(tab.document);
|
|
}
|
|
|
|
// Don't call update to enable/disable breakpoints when they are
|
|
// all deactivated
|
|
if (force || enableBreakpoints || (action != "enable" && action != "disable"))
|
|
updateBreakpointAtDebugger(breakpoint, action);
|
|
|
|
changed = true;
|
|
settings.save();
|
|
}
|
|
|
|
/***** Methods *****/
|
|
|
|
function setCondition(breakpoint, condition, ignoreXml) {
|
|
breakpoint.data.condition = condition;
|
|
updateBreakpoint(breakpoint, "condition");
|
|
|
|
ignoreXml || model._signal("change");
|
|
|
|
return true;
|
|
}
|
|
|
|
function enableBreakpoint(breakpoint, ignoreXml) {
|
|
breakpoint.data.enabled = true;
|
|
updateBreakpoint(breakpoint, "enable");
|
|
|
|
ignoreXml || model._signal("change");
|
|
|
|
return true;
|
|
}
|
|
|
|
function disableBreakpoint(breakpoint, ignoreXml) {
|
|
breakpoint.data.enabled = false;
|
|
updateBreakpoint(breakpoint, "disable");
|
|
|
|
ignoreXml || model._signal("change");
|
|
|
|
return true;
|
|
}
|
|
|
|
function setBreakpoint(breakpoint, noEvent) {
|
|
// Ignore if the breakpoint already exists
|
|
for (var i = 0, l = breakpoints.length, bp; i < l; i++) {
|
|
if ((bp = breakpoints[i]).equals(breakpoint, true)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (breakpoint.hidden)
|
|
return;
|
|
|
|
// Make sure we have a breakpoint object
|
|
if (!(breakpoint instanceof Breakpoint))
|
|
breakpoint = new Breakpoint(breakpoint);
|
|
|
|
breakpoints.push(breakpoint);
|
|
model._signal("change");
|
|
|
|
if (!noEvent) // Prevent recursion during init
|
|
updateBreakpoint(breakpoint, "add");
|
|
|
|
return breakpoint;
|
|
}
|
|
|
|
function clearBreakpoint(breakpoint, ignoreXml, silent) {
|
|
breakpoints.remove(breakpoint);
|
|
if (!silent)
|
|
updateBreakpoint(breakpoint, "remove");
|
|
|
|
ignoreXml || model._signal("change");
|
|
}
|
|
|
|
function redrawBreakpoint(bp) {
|
|
var tab = tabs.findTab(bp.path);
|
|
if (!tab) return;
|
|
|
|
updateDocument(tab.document);
|
|
|
|
model._signal("change", bp);
|
|
}
|
|
|
|
function findBreakpoint(path, line, multi) {
|
|
if (typeof path == "object") {
|
|
line = path.line;
|
|
path = path.path;
|
|
}
|
|
|
|
var match = { path: path };
|
|
if (line || line === 0) match.line = line;
|
|
|
|
var bp, list = [];
|
|
for (var i = 0, l = breakpoints.length; i < l; i++) {
|
|
bp = breakpoints[i];
|
|
// loc = bp.actual || bp;
|
|
|
|
// if (bp.path == path && (!line || loc.line == line)) {
|
|
if (bp.equals(match)) {
|
|
if (!multi) return bp;
|
|
else list.push(bp);
|
|
}
|
|
}
|
|
|
|
return multi ? list : false;
|
|
}
|
|
|
|
function findBreakpoints(path, line) {
|
|
return findBreakpoint(path, line, true);
|
|
}
|
|
|
|
function gotoBreakpoint(bp, line, column) {
|
|
var path;
|
|
|
|
if (bp instanceof Breakpoint) {
|
|
var loc = bp.actual || bp;
|
|
path = bp.path;
|
|
line = loc.line - 1;
|
|
column = loc.column;
|
|
}
|
|
else if (typeof bp == "object") {
|
|
return gotoBreakpoint(findBreakpoint(bp));
|
|
}
|
|
else {
|
|
path = bp;
|
|
}
|
|
|
|
if (isNaN(line)) line = undefined;
|
|
if (isNaN(column)) column = undefined;
|
|
|
|
debug.openFile({
|
|
path: path,
|
|
line: line,
|
|
column: column
|
|
});
|
|
}
|
|
|
|
function activateAll(force) {
|
|
if (enableBreakpoints && !force) return;
|
|
|
|
enableBreakpoints = true;
|
|
settings.set("user/breakpoints/@active", true);
|
|
|
|
breakpoints.forEach(function(bp) {
|
|
if (bp.enabled)
|
|
updateBreakpoint({ id: bp.id, enabled: true }, "enable", force);
|
|
});
|
|
|
|
if (drawn)
|
|
list.renderer.unsetStyle("listBPDisabled");
|
|
|
|
toggleBreakpoints(true);
|
|
}
|
|
|
|
function deactivateAll(force) {
|
|
if (!enableBreakpoints && !force) return;
|
|
|
|
settings.set("user/breakpoints/@active", false);
|
|
|
|
breakpoints.forEach(function(bp) {
|
|
updateBreakpoint({ id: bp.id, enabled: false }, "disable", force);
|
|
});
|
|
|
|
if (drawn)
|
|
list.renderer.setStyle("listBPDisabled");
|
|
|
|
enableBreakpoints = false;
|
|
toggleBreakpoints(false);
|
|
}
|
|
|
|
/***** Lifecycle *****/
|
|
|
|
plugin.on("load", function() {
|
|
load();
|
|
plugin.once("draw", draw);
|
|
});
|
|
plugin.on("enable", function() {
|
|
if (!enableBreakpoints && drawn)
|
|
list.renderer.setStyle("listBPDisabled");
|
|
});
|
|
plugin.on("disable", function() {
|
|
|
|
});
|
|
plugin.on("unload", function() {
|
|
loaded = false;
|
|
drawn = false;
|
|
drawnCondition = false;
|
|
});
|
|
|
|
/***** Register and define API *****/
|
|
|
|
/**
|
|
* The breakpoints panel for the {@link debugger Cloud9 debugger}.
|
|
*
|
|
* This panel shows a list of all the breakpoints and allows the user
|
|
* to remove, disable and enable the breakpoints.
|
|
*
|
|
* @singleton
|
|
* @extends DebugPanel
|
|
**/
|
|
plugin.freezePublicAPI({
|
|
/**
|
|
* A list of breakpoints that are set.
|
|
* @property {debugger.Breakpoint[]} breakpoints
|
|
* @readonly
|
|
*/
|
|
get breakpoints() { return breakpoints.slice(0); },
|
|
|
|
/**
|
|
* Sets or retrieves whether the debugger should break when it hits
|
|
* a breakpoint.
|
|
* @property {Boolean} enableBreakpoints
|
|
* @readonly
|
|
*/
|
|
get enableBreakpoints() { return enableBreakpoints; },
|
|
set enableBreakpoints(v) {
|
|
enableBreakpoints = v;
|
|
toggleBreakpoints(v);
|
|
},
|
|
|
|
/**
|
|
* Sets the condition expression of a breakpoint.
|
|
* @param {debugger.Breakpoint} breakpoint The breakpoint to set the condition on.
|
|
* @param {String} condition An expression that needs to be true for the debugger to break on the breakpoint.
|
|
*/
|
|
setCondition: setCondition,
|
|
|
|
/**
|
|
* Flags a breakpoint to not be ignored when the debugger hits it.
|
|
* @param {debugger.Breakpoint} breakpoint The breakpoint to enable.
|
|
*/
|
|
enableBreakpoint: enableBreakpoint,
|
|
|
|
/**
|
|
* Flags a breakpoint to be ignored when the debugger hits it.
|
|
* @param {debugger.Breakpoint} breakpoint The breakpoint to disable.
|
|
*/
|
|
disableBreakpoint: disableBreakpoint,
|
|
|
|
/**
|
|
* Displays a breakpoint in the ace editor.
|
|
* @param {debugger.Breakpoint} breakpoint The breakpoint to display.
|
|
*/
|
|
gotoBreakpoint: gotoBreakpoint,
|
|
|
|
/**
|
|
* Adds a breakpoint to the list of breakpoints.
|
|
* @param {debugger.Breakpoint} breakpoint The breakpoint to add.
|
|
*/
|
|
setBreakpoint: setBreakpoint,
|
|
|
|
/**
|
|
* Removes a breakpoint from the list of breakpoints.
|
|
* @param {debugger.Breakpoint} breakpoint The breakpoint to remove.
|
|
*/
|
|
clearBreakpoint: clearBreakpoint,
|
|
});
|
|
|
|
register(null, {
|
|
breakpoints: plugin
|
|
});
|
|
}
|
|
});
|