kopia lustrzana https://github.com/c9/core
507 wiersze
18 KiB
JavaScript
507 wiersze
18 KiB
JavaScript
define(function(require, exports, module) {
|
|
main.consumes = [
|
|
"DebugPanel", "settings", "ui", "util", "debugger", "ace", "commands",
|
|
"menus", "Menu", "MenuItem", "Divider", "panels", "layout"
|
|
];
|
|
main.provides = ["watches"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var DebugPanel = imports.DebugPanel;
|
|
var settings = imports.settings;
|
|
var ui = imports.ui;
|
|
var debug = imports.debugger;
|
|
var util = imports.util;
|
|
var layout = imports.layout;
|
|
var menus = imports.menus;
|
|
var commands = imports.commands;
|
|
var ace = imports.ace;
|
|
var Menu = imports.Menu;
|
|
var MenuItem = imports.MenuItem;
|
|
var Divider = imports.Divider;
|
|
var panels = imports.panels;
|
|
|
|
var keys = require("ace/lib/keys");
|
|
var markup = require("text!./watches.xml");
|
|
var Variable = require("./data/variable");
|
|
var Tree = require("ace_tree/tree");
|
|
var TreeData = require("./variablesdp");
|
|
var TreeEditor = require("ace_tree/edit");
|
|
|
|
/***** Initialization *****/
|
|
|
|
var plugin = new DebugPanel("Ajax.org", main.consumes, {
|
|
caption: "Watch Expressions",
|
|
index: 100
|
|
});
|
|
var emit = plugin.getEmitter();
|
|
|
|
var count = 0;
|
|
var watches = [];
|
|
var dirty = false;
|
|
var dbg, model, datagrid, errorWatch;
|
|
|
|
var loaded = false;
|
|
function load() {
|
|
if (loaded) return false;
|
|
loaded = true;
|
|
|
|
model = new TreeData();
|
|
model.emptyMessage = "Type an expression here...";
|
|
model.$sortNodes = false;
|
|
|
|
model.loadChildren = function(node, callback) {
|
|
if (node.isNew)
|
|
return callback(true);
|
|
|
|
emit("expand", {
|
|
variable: node,
|
|
expand: callback
|
|
});
|
|
};
|
|
|
|
model.columns = [{
|
|
caption: "Expression",
|
|
match: "name",
|
|
value: "name",
|
|
width: "60%",
|
|
icon: true,
|
|
type: "tree",
|
|
editor: "textbox"
|
|
}, {
|
|
caption: "Value",
|
|
value: "value",
|
|
width: "40%",
|
|
editor: "textbox"
|
|
}, {
|
|
caption: "Type",
|
|
value: "type",
|
|
width: "55"
|
|
}];
|
|
|
|
// Set and clear the dbg variable
|
|
debug.on("attach", function(e) {
|
|
dbg = e.implementation;
|
|
updateAll();
|
|
});
|
|
debug.on("detach", function(e) {
|
|
dbg = null;
|
|
});
|
|
debug.on("stateChange", function(e) {
|
|
if (errorWatch)
|
|
removeWatch(errorWatch);
|
|
|
|
plugin[e.action]();
|
|
if (e.action == "enable")
|
|
updateAll();
|
|
});
|
|
debug.on("framesLoad", function(e) {
|
|
// Update Watchers
|
|
updateAll();
|
|
});
|
|
debug.on("exception", function(e) {
|
|
if (errorWatch)
|
|
removeWatch(errorWatch);
|
|
errorWatch = e.exception;
|
|
addWatch(errorWatch);
|
|
model.expand(errorWatch);
|
|
});
|
|
|
|
plugin.on("expand", function(e) {
|
|
if (e.variable.parent == model.root)
|
|
return e.expand && e.expand(e.variable.error);
|
|
dbg.getProperties(e.variable, function(err, properties) {
|
|
updateVariable(e.variable, properties);
|
|
e.expand && e.expand();
|
|
});
|
|
});
|
|
|
|
// Add Watch hook into ace
|
|
commands.addCommand({
|
|
name: "addwatchfromselection",
|
|
bindKey: { mac: "Command-Shift-C", win: "Ctrl-Shift-C" },
|
|
hint: "Add the selection as a watch expression",
|
|
isAvailable: function(editor) {
|
|
var ace = dbg && editor && editor.ace;
|
|
return ace && !ace.selection.isEmpty();
|
|
},
|
|
exec: function(editor) {
|
|
if (!editor.ace.selection.isEmpty())
|
|
addWatch(editor.ace.getCopyText());
|
|
}
|
|
}, plugin);
|
|
|
|
// right click context item in ace
|
|
ace.getElement("menu", function(menu) {
|
|
menus.addItemToMenu(menu, new ui.item({
|
|
caption: "Add As Watch Expression",
|
|
command: "addwatchfromselection"
|
|
}), 600, plugin);
|
|
});
|
|
|
|
// restore the variables from the IDE settings
|
|
settings.on("read", function (e) {
|
|
(settings.getJson("state/watches") || []).forEach(function(name) {
|
|
watches.push(new Variable({
|
|
name: name,
|
|
ref: "fromsettings" + count++
|
|
}));
|
|
});
|
|
|
|
reloadModel();
|
|
|
|
if (dbg)
|
|
updateAll();
|
|
});
|
|
|
|
settings.on("write", function (e) {
|
|
if (dirty) {
|
|
settings.setJson("state/watches", watches.filter(function(w) {
|
|
return w !== errorWatch;
|
|
}).map(function(w) {
|
|
return w.name;
|
|
}));
|
|
dirty = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
var drawn;
|
|
function draw(options) {
|
|
if (drawn) return;
|
|
drawn = true;
|
|
|
|
// Create UI elements
|
|
ui.insertMarkup(options.aml, markup, plugin);
|
|
|
|
var datagridEl = plugin.getElement("datagrid");
|
|
datagrid = new Tree(datagridEl.$ext);
|
|
datagrid.setTheme({ cssClass: "blackdg" });
|
|
datagrid.setOption("maxLines", 200);
|
|
|
|
layout.on("eachTheme", function(e) {
|
|
var height = parseInt(ui.getStyleRule(".blackdg .row", "height"), 10) || 24;
|
|
// model.rowHeightInner = height - 1;
|
|
model.rowHeight = height;
|
|
|
|
if (e.changed) datagrid.resize(true);
|
|
});
|
|
|
|
datagrid.setDataProvider(model);
|
|
datagrid.edit = new TreeEditor(datagrid);
|
|
panels.on("afterAnimate", function(e) {
|
|
if (panels.isActive("debugger"))
|
|
datagrid && datagrid.resize();
|
|
});
|
|
|
|
reloadModel();
|
|
|
|
var contextMenu = new Menu({
|
|
items: [
|
|
new MenuItem({ value: "edit1", caption: "Edit Watch Expression" }),
|
|
new MenuItem({ value: "edit2", caption: "Edit Watch Value" }),
|
|
new Divider(),
|
|
new MenuItem({ value: "remove", caption: "Remove Watch Expression" }),
|
|
]
|
|
}, plugin);
|
|
contextMenu.on("itemclick", function(e) {
|
|
if (e.value == "edit1")
|
|
datagrid.edit.startRename(null, 0);
|
|
else if (e.value == "edit2")
|
|
datagrid.edit.startRename(null, 1);
|
|
else if (e.value == "remove")
|
|
datagrid.execCommand("delete");
|
|
});
|
|
contextMenu.on("show", function(e) {
|
|
var selected = datagrid.selection.getCursor();
|
|
var isNew = selected && selected.isNew;
|
|
var isProp = selected.parent != model.root;
|
|
contextMenu.items[0].disabled = !selected || isProp;
|
|
contextMenu.items[1].disabled = !selected || !!isNew;
|
|
contextMenu.items[3].disabled = !selected || !!isNew || isProp;
|
|
});
|
|
|
|
datagridEl.setAttribute("contextmenu", contextMenu.aml);
|
|
|
|
datagrid.on("delete", function(e) {
|
|
var nodes = datagrid.selection.getSelectedNodes();
|
|
nodes.forEach(function (node) {
|
|
var idx = watches.indexOf(node);
|
|
if (idx != -1) {
|
|
model._signal("remove", node);
|
|
watches.splice(idx, 1);
|
|
}
|
|
});
|
|
reloadModel();
|
|
dirty = true;
|
|
settings.save();
|
|
});
|
|
|
|
var justEdited = false;
|
|
|
|
datagrid.on("rename", function(e) {
|
|
var node = e.node;
|
|
var name = e.value;
|
|
var value = node.value;
|
|
var isNew = node.isNew;
|
|
var column = e.column;
|
|
var parents = [];
|
|
var variable, oldValue;
|
|
|
|
// Delete a watch by removing the expression
|
|
if (!name) {
|
|
datagrid.execCommand("delete");
|
|
return;
|
|
}
|
|
|
|
// If we've filled a new watch remove the new attribute
|
|
if (isNew) {
|
|
variable = new Variable({
|
|
name: name,
|
|
value: value,
|
|
ref: node.ref
|
|
});
|
|
watches.push(variable);
|
|
}
|
|
else {
|
|
// variable = node;
|
|
if (watches.indexOf(node) != -1)
|
|
variable = node;
|
|
else if (node.ref)
|
|
variable = findVariable(node.ref, parents);
|
|
|
|
if (column.value == "value") {
|
|
oldValue = variable.value;
|
|
value = variable.value = name;
|
|
}
|
|
else {
|
|
variable.name = name;
|
|
isNew = true;
|
|
}
|
|
|
|
if (variable.error) {
|
|
isNew = true;
|
|
variable.ref = null;
|
|
}
|
|
}
|
|
|
|
dirty = true;
|
|
settings.save();
|
|
reloadModel();
|
|
|
|
setWatch(variable, value, isNew, oldValue, node, parents);
|
|
});
|
|
|
|
datagrid.on("beforeRename", function(e) {
|
|
// Don't allow setting the value of new variables
|
|
if (e.column.caption == "Value"
|
|
&& (e.node.ref + "").substr(0, 3) == "new") {
|
|
datagrid.edit.startRename(null, 0);
|
|
return e.preventDefault();
|
|
}
|
|
|
|
// When editing a property name, always force editing the value
|
|
if (e.column.caption == "Expression"
|
|
&& e.node.parent != model.root) {
|
|
datagrid.edit.startRename(null, 1);
|
|
return e.preventDefault();
|
|
}
|
|
|
|
if (e.column.caption != "Expression"
|
|
&& dbg && !dbg.features.updateWatchedVariables)
|
|
return e.preventDefault();
|
|
});
|
|
|
|
datagrid.on("rename", function(e) {
|
|
justEdited = true;
|
|
setTimeout(function() { justEdited = false; }, 500);
|
|
});
|
|
|
|
datagrid.textInput.getElement().addEventListener("keydown", function(e) {
|
|
var cursor = datagrid.selection.getCursor();
|
|
var key = keys[e.keyCode] || "";
|
|
if (key.length == 1 || key.substr(0, 3) == "num" && cursor && !justEdited)
|
|
datagrid.edit.startRename(cursor, 0);
|
|
}, true);
|
|
|
|
datagrid.textInput.getElement().addEventListener("keyup", function(e) {
|
|
var cursor = datagrid.selection.getCursor();
|
|
if (e.keyCode == 13 && cursor && !justEdited)
|
|
datagrid.edit.startRename(cursor, 0);
|
|
}, true);
|
|
}
|
|
|
|
/***** Methods *****/
|
|
|
|
function addWatch(expression) {
|
|
var variable;
|
|
|
|
if (expression instanceof Variable) {
|
|
variable = expression;
|
|
}
|
|
else {
|
|
variable = new Variable({
|
|
name: expression,
|
|
value: "",
|
|
ref: ""
|
|
});
|
|
setWatch(variable, null, true, null, {}, []);
|
|
}
|
|
watches.push(variable);
|
|
|
|
reloadModel();
|
|
|
|
dirty = true;
|
|
settings.save();
|
|
}
|
|
|
|
function removeWatch(variable) {
|
|
watches.splice(watches.indexOf(variable), 1);
|
|
reloadModel();
|
|
|
|
dirty = true;
|
|
settings.save();
|
|
}
|
|
|
|
function setWatch(variable, value, isNew, oldValue, node, parents) {
|
|
if (!dbg)
|
|
return; // We've apparently already disconnected.
|
|
|
|
variable.status = "pending";
|
|
// Editing watches in the current or global frame
|
|
// Execute expression
|
|
if (isNew) {
|
|
dbg.evaluate(variable.name, debug.activeFrame,
|
|
!debug.activeFrame, true, function(err, serverVariable) {
|
|
if (err) {
|
|
variable.json = {
|
|
name: variable.name,
|
|
value: err.message,
|
|
error: true
|
|
};
|
|
updateVariable(variable, [], node, true);
|
|
return;
|
|
}
|
|
|
|
variable.json = serverVariable.json;
|
|
|
|
updateVariable(variable,
|
|
variable.properties || [], node);
|
|
});
|
|
}
|
|
// Set new value of a property
|
|
else {
|
|
dbg.setVariable(variable, value, debug.activeFrame, function(err) {
|
|
if (err) {
|
|
variable.value = oldValue;
|
|
updateVariable(variable, [], node, true);
|
|
return;
|
|
}
|
|
|
|
// Reload properties of the variable
|
|
dbg.getProperties(variable, function(err, properties) {
|
|
updateVariable(variable, properties, node);
|
|
});
|
|
});
|
|
}
|
|
|
|
emit("setWatch", {
|
|
name: node.name,
|
|
value: value,
|
|
node: node,
|
|
isNew: isNew,
|
|
variable: variable,
|
|
parents: parents
|
|
});
|
|
}
|
|
|
|
function updateAll() {
|
|
watches.forEach(function(variable) {
|
|
setWatch(variable, undefined, true, null, variable, []);
|
|
});
|
|
}
|
|
|
|
function findVariable(ref, parents) {
|
|
if (typeof ref == "object")
|
|
ref = ref.getAttribute("ref");
|
|
|
|
var result;
|
|
for (var i = 0, l = watches.length; i < l; i++) {
|
|
if (watches[i].ref == ref)
|
|
return watches[i];
|
|
|
|
result = watches[i].findVariable(ref, null, parents);
|
|
if (result) return result;
|
|
}
|
|
}
|
|
|
|
function updateVariable(variable, properties, node, error) {
|
|
// Pass node for recursive trees
|
|
variable.error = error;
|
|
if (!variable.parent || variable.parent == model.root)
|
|
reloadModel();
|
|
else
|
|
model.updateNode(variable);
|
|
}
|
|
|
|
function reloadModel() {
|
|
model.newWatchNode = model.newWatchNode || {
|
|
name: model.emptyMessage,
|
|
className: "newwatch",
|
|
fullWidth: true,
|
|
isNew: true,
|
|
};
|
|
model.setRoot({
|
|
items: [].concat(watches, model.newWatchNode),
|
|
$sorted: true
|
|
});
|
|
}
|
|
|
|
/***** Lifecycle *****/
|
|
|
|
plugin.on("load", function() {
|
|
load();
|
|
plugin.once("draw", draw);
|
|
});
|
|
plugin.on("enable", function() {
|
|
|
|
});
|
|
plugin.on("disable", function() {
|
|
|
|
});
|
|
plugin.on("unload", function() {
|
|
loaded = false;
|
|
drawn = false;
|
|
});
|
|
|
|
/***** Register and define API *****/
|
|
|
|
/**
|
|
* The watch expression panel for the {@link debugger Cloud9 debugger}.
|
|
*
|
|
* This panel allows a user to add small expressions that are evaluated
|
|
* continuously, displaying the result of the expression in the UI. This
|
|
* allows a user to monitor what is going on while stepping through the
|
|
* code.
|
|
*
|
|
* @singleton
|
|
* @extends DebugPanel
|
|
**/
|
|
plugin.freezePublicAPI({
|
|
/**
|
|
* A list of variables that are watched.
|
|
* @param {debugger.Variable[]} watches The list of variables watched.
|
|
*/
|
|
get watches() { return watches; },
|
|
|
|
/**
|
|
* Re-evaluate all watch expressions.
|
|
*/
|
|
updateAll: updateAll
|
|
});
|
|
|
|
register(null, {
|
|
watches: plugin
|
|
});
|
|
}
|
|
}); |