diff --git a/client/apf/elements/codeeditor.js b/client/apf/elements/codeeditor.js
new file mode 100644
index 00000000000..e27a425ceb3
--- /dev/null
+++ b/client/apf/elements/codeeditor.js
@@ -0,0 +1,684 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ *
+ */
+
+// #ifdef __AMLCODEEDITOR || __INC_ALL
+
+/**
+ * Element allowing the user to type code.
+ *
+ * @constructor
+ * @define codeeditor
+ * @addnode elements
+ *
+ * @inherits apf.StandardBinding
+ *
+ * @author Ruben Daniels (ruben AT ajax DOT org)
+ * @author Fabian Jakobs (fabian AT ajax DOT org)
+ * @version %I%, %G%
+ * @since 0.1
+ */
+
+define(function(require, exports, module) {
+
+var Editor = require("ace/editor");
+var EditSession = require("ace/edit_session");
+var VirtualRenderer = require("ace/virtual_renderer");
+var UndoManager = require("ace/undomanager");
+var Range = require("ace/range");
+require("ace/lib/fixoldbrowsers");
+
+Editor = Editor.Editor;
+EditSession = EditSession.EditSession;
+VirtualRenderer = VirtualRenderer.VirtualRenderer;
+UndoManager = UndoManager.UndoManager;
+Range = Range.Range;
+
+apf.codeeditor = module.exports = function(struct, tagName) {
+ this.$init(tagName || "codeeditor", apf.NODE_VISIBLE, struct);
+
+ this.documents = [];
+ this.$cache = {};
+
+ //this.setProperty("overwrite", false);
+ this.setProperty("line", 1);
+ this.setProperty("col", 1);
+};
+
+(function() {
+ this.implement(
+ //#ifdef __WITH_DATAACTION
+ apf.DataAction
+ //#endif
+ );
+
+ this.$focussable = true; // This object can get the focus
+ this.$childProperty = "value";
+ this.$isTextInput = true;
+
+ this.value = "";
+ this.multiline = true;
+ this.caching = true;
+
+ this.$booleanProperties["activeline"] = true;
+ this.$booleanProperties["caching"] = true;
+ this.$booleanProperties["readonly"] = true;
+ this.$booleanProperties["activeline"] = true;
+ this.$booleanProperties["showinvisibles"] = true;
+ this.$booleanProperties["showprintmargin"] = true;
+ this.$booleanProperties["overwrite"] = true;
+ this.$booleanProperties["softtabs"] = true;
+ this.$booleanProperties["gutter"] = true;
+ this.$booleanProperties["highlightselectedword"] = true;
+ this.$booleanProperties["autohidehorscrollbar"] = true;
+ this.$booleanProperties["behaviors"] = true;
+ this.$booleanProperties["folding"] = true;
+
+ this.$supportedProperties.push("value", "syntax", "activeline", "selectstyle",
+ "caching", "readonly", "showinvisibles", "showprintmargin", "printmargincolumn",
+ "overwrite", "tabsize", "softtabs", "debugger", "model-breakpoints", "scrollspeed",
+ "theme", "gutter", "highlightselectedword", "autohidehorscrollbar",
+ "behaviors", "folding");
+
+ this.$getCacheKey = function(value) {
+ if (typeof value == "string") {
+ var key = this.xmlRoot
+ ? this.xmlRoot.getAttribute(apf.xmldb.xmlIdTag)
+ : value;
+ }
+ else if (value.nodeType) {
+ var key = value.getAttribute(apf.xmldb.xmlIdTag);
+ }
+
+ return key;
+ };
+
+ this.clearCacheItem = function(xmlNode) {
+ if (!this.caching)
+ return;
+
+ var key = this.$getCacheKey(xmlNode);
+ if (key)
+ delete this.$cache[key];
+ };
+
+ this.addEventListener("unloadmodel", function() {
+ this.syncValue();
+ });
+
+ /**
+ * @attribute {String} value the text of this element
+ * @todo apf3.0 check use of this.$propHandlers["value"].call
+ */
+ this.$propHandlers["value"] = function(value){ //@todo apf3.0 add support for the range object as a value
+ var doc, key,
+ _self = this;
+
+ if (this.caching)
+ key = this.$getCacheKey(value);
+
+ //Assuming document
+ if (value instanceof EditSession)
+ doc = value;
+
+ if (!doc && key)
+ doc = this.$cache[key];
+
+ if (!doc) {
+ if (value.nodeType) {
+ apf.xmldb.addNodeListener(value.nodeType == 1
+ ? value : value.parentNode, this);
+ }
+
+ doc = new EditSession(typeof value == "string"
+ ? value
+ : (value.nodeType > 1 && value.nodeType < 5 //@todo replace this by a proper function
+ ? value.nodeValue
+ : value.firstChild && value.firstChild.nodeValue || ""));
+
+ doc.cacheId = key;
+ doc.setUndoManager(new UndoManager());
+
+ if (key)
+ this.$cache[key] = doc;
+ }
+ //@todo value can also be an xml node and should be updated in a similar fashion as above
+ else if (typeof value == "string" && !doc.hasValue) {
+ //@todo big hack!
+ doc.setValue(value);
+ this.$editor.moveCursorTo(0, 0);
+ doc.hasValue = true;
+ }
+
+ _self.$getMode(_self.syntax, function(mode) {
+ doc.setMode(mode);
+ });
+
+ doc.setTabSize(parseInt(_self.tabsize, 10));
+ doc.setUseSoftTabs(_self.softtabs);
+ doc.setUseWrapMode(_self.wrapmode);
+ doc.setWrapLimitRange(_self.wraplimitmin, _self.wraplimitmax);
+ doc.setFoldStyle(_self.folding ? "markbegin" : "manual");
+
+ _self.$removeDocListeners && _self.$removeDocListeners();
+ _self.$removeDocListeners = _self.$addDocListeners(doc);
+
+ _self.$editor.setShowPrintMargin(_self.showprintmargin);
+
+ // remove existing markers
+ _self.$clearMarker();
+
+ _self.$editor.setSession(doc);
+
+ _self.$updateMarker();
+ _self.$updateBreakpoints(doc);
+ };
+
+ this.$addDocListeners = function(doc) {
+ var _self = this;
+ var onCursorChange = function() {
+ var cursor = doc.getSelection().getCursor();
+ _self.setProperty("line", cursor.row+1);
+ _self.setProperty("col", cursor.column+1);
+ };
+
+ doc.getSelection().addEventListener("changeCursor", onCursorChange);
+
+ onCursorChange();
+
+ return function() {
+ doc.getSelection().removeEventListener("changeCursor", onCursorChange);
+ };
+ };
+
+ this.$clearMarker = function () {
+ if (this.$marker) {
+ this.$editor.renderer.removeGutterDecoration(this.$lastRow[0], this.$lastRow[1]);
+ this.$editor.getSession().removeMarker(this.$marker);
+ this.$marker = null;
+ }
+ };
+
+ /**
+ * Indicates whether we are going to set a marker
+ */
+ this.$updateMarkerPrerequisite = function () {
+ if (!this.$debugger) {
+ return;
+ }
+ var frame = this.$debugger.activeframe;
+ if (!frame) {
+ return;
+ }
+
+ // when running node with 'debugbrk' it will auto break on the first line of executable code
+ // we don't want to really break here so we put this:
+ if (frame.getAttribute("name") === "anonymous(exports, require, module, __filename, __dirname)"
+ && frame.getAttribute("index") === "0" && frame.getAttribute("line") === "0") {
+
+ var fileNameNode = frame.selectSingleNode("//frame/vars/item[@name='__filename']");
+ var fileName = fileNameNode ? fileNameNode.getAttribute("value") : "";
+ var model = this["model-breakpoints"].data;
+
+ // is there a breakpoint on the exact same line and file? then continue
+ if (fileName && model && model.selectSingleNode("//breakpoints/breakpoint[@script='" + fileName + "' and @line=0]")) {
+ return frame;
+ }
+
+ return;
+ }
+
+ return frame;
+ };
+
+ this.$updateMarker = function () {
+ this.$clearMarker();
+
+ var frame = this.$updateMarkerPrerequisite();
+ if (!frame) {
+ return;
+ }
+
+ var script = this.xmlRoot;
+ if (script.getAttribute("scriptid") !== frame.getAttribute("scriptid")) {
+ return;
+ }
+
+ var head = this.$debugger.$mdlStack.queryNode("frame[1]");
+ var isTop = frame == head;
+ var lineOffset = parseInt(script.getAttribute("lineoffset") || "0", 10);
+ var row = parseInt(frame.getAttribute("line"), 10) - lineOffset;
+ var range = new Range(row, 0, row + 1, 0);
+ this.$marker = this.$editor.getSession().addMarker(range, isTop ? "ace_step" : "ace_stack", "line");
+ var type = isTop ? "arrow" : "stack";
+ this.$lastRow = [row, type];
+ this.$editor.renderer.addGutterDecoration(row, type);
+ this.$editor.gotoLine(row + 1, parseInt(frame.getAttribute("column"), 10));
+ };
+
+ this.$updateBreakpoints = function(doc) {
+ doc = doc || this.$editor.getSession();
+
+ doc.setBreakpoints([]);
+ if (!this.$breakpoints)
+ return;
+
+ if (this.xmlRoot) {
+ var scriptName = this.xmlRoot.getAttribute("scriptname");
+ if (!scriptName)
+ return;
+
+ var breakpoints = this.$breakpoints.queryNodes("//breakpoint[@script='" + scriptName + "']");
+
+ var rows = [];
+ for (var i=0; i");
+ }
+ model.load("" + xml.join("") + "");
+ });
+ });
+ };
+
+ this.attach = function(tabId, callback) {
+ var dbg;
+
+ if (dbg = this.$debuggers[tabId])
+ return callback(null, dbg)
+
+ var self = this;
+ this.$connect(function() {
+ self.$v8ds.attach(tabId, function() {
+ dbg = new APFV8Debugger(new V8Debugger(tabId, self.$v8ds), this);
+ self.$debuggers[tabId] = dbg;
+ callback(null, dbg);
+ });
+ });
+ };
+
+ this.detach = function(dbg, callback) {
+ var self = this;
+ for (var id in this.$debuggers) {
+ if (this.$debuggers[id] == dbg) {
+ this.$v8ds.detach(id, function(err) {
+ delete self.$debuggers[id];
+ dbg.dispatchEvent("detach");
+ callback && callback(err);
+ });
+ break;
+ }
+ }
+ };
+
+ this.disconnect = function(callback) {
+ var debuggers = [];
+ for (var id in this.$debuggers) {
+ debuggers.push(id);
+ }
+
+ var self = this;
+ var detachNext = function() {
+ if (debuggers.length) {
+ var id = debuggers.shift();
+ var dbg = self.$debuggers[id]
+ self.$v8ds.detach(id, function() {
+ detachNext();
+ dbg.dispatchEvent("detach");
+ });
+ } else {
+ self.$socket.close();
+ self.$debuggers = {};
+ self.dispatchEvent("disconnect", {});
+ callback && callback();
+ }
+ }
+
+ detachNext();
+ };
+
+}).call(ChromeDebugHost.prototype = new apf.Class());
+
+});
+// #endif
\ No newline at end of file
diff --git a/client/apf/elements/dbg/v8debugger.js b/client/apf/elements/dbg/v8debugger.js
new file mode 100644
index 00000000000..1868c5c8e28
--- /dev/null
+++ b/client/apf/elements/dbg/v8debugger.js
@@ -0,0 +1,529 @@
+// #ifdef __AMLDEBUGGER || __INC_ALL
+if (apf.hasRequireJS) define("apf/elements/dbg/v8debugger",
+ ["module", "debug/Breakpoint"],
+ function(module, Breakpoint) {
+
+var V8Debugger = module.exports = function(dbg, host) {
+ this.$init();
+
+ this.$debugger = dbg;
+ this.$host = host;
+
+ this.$breakpoints = {};
+
+ var _self = this;
+ dbg.addEventListener("changeRunning", function(e) {
+ _self.dispatchEvent("changeRunning", e);
+ if (dbg.isRunning()) {
+ _self.setFrame(null);
+ }
+ });
+ dbg.addEventListener("break", function(e) {
+ _self.dispatchEvent("break", e);
+ });
+ dbg.addEventListener("afterCompile", function(e) {
+ _self.dispatchEvent("afterCompile", {script: apf.getXml(_self.$getScriptXml(e.data.script))});
+ });
+
+ this.setFrame(null);
+};
+
+(function() {
+ var hasChildren = {
+ "object": 8,
+ "function": 4
+ };
+
+ this.stripPrefix = "";
+
+ this.setStrip = function(stripPrefix) {
+ this.stripPrefix = stripPrefix;
+ };
+
+ this.$strip = function(str) {
+ if (!this.stripPrefix)
+ return str;
+
+ return str.indexOf(this.stripPrefix) === 0
+ ? str.slice(this.stripPrefix.length)
+ : str;
+ };
+
+ this.isRunning = function() {
+ return this.$debugger.isRunning();
+ };
+
+ this.scripts = function(model, callback) {
+ var _self = this;
+ this.$debugger.scripts(4, null, false, function(scripts) {
+ var xml = [];
+ for (var i = 0, l = scripts.length; i < l; i++) {
+ var script = scripts[i];
+ if (script.name && script.name.indexOf("chrome-extension://") === 0)
+ continue;
+ xml.push(_self.$getScriptXml(script));
+ }
+ model.load("" + xml.join("") + "");
+ callback();
+ });
+ };
+
+ this.$getScriptXml = function(script) {
+ return [
+ ""
+ ].join("");
+ };
+
+ function getId(frame){
+ return (frame.func.name || frame.func.inferredName || (frame.line + frame.position));
+ }
+
+ this.$isEqual = function(xmlFrameSet, frameSet){
+ if (xmlFrameSet.length != frameSet.length)
+ return false;
+
+ var xmlFirst = xmlFrameSet[0];
+ var first = frameSet[0];
+ if (xmlFirst.getAttribute("scriptid") != first.func.scriptId)
+ return false;
+ if (xmlFirst.getAttribute("id") != getId(first))
+ return false;
+ //if (xmlFirst.selectNodes("vars/item").length != (1 + first.arguments.length + first.locals.length))
+ //return false;
+
+ //@todo check for ref?? might fail for 2 functions in the same file with the same name in a different context
+ return true;
+ };
+
+ /**
+ * Assumptions:
+ * - .index stays the same
+ * - sequence in the array stays the same
+ * - ref stays the same when stepping in the same context
+ */
+ this.$updateFrame = function(xmlFrame, frame){
+ //With code insertion, line/column might change??
+ xmlFrame.setAttribute("line", frame.line);
+ xmlFrame.setAttribute("column", frame.column);
+
+ var i, j, l;
+ var vars = xmlFrame.selectNodes("vars/item");
+ var fVars = frame.arguments;
+ for (i = 1, j = 0, l = fVars.length; j < l; j++) { //i = 1 to skin this
+ if (fVars[j].name)
+ this.$updateVar(vars[i++], fVars[j]);
+ }
+ fVars = frame.locals;
+ for (j = 0, l = frame.locals.length; j < l; j++) {
+ if (fVars[j].name !== ".arguments")
+ this.$updateVar(vars[i++], fVars[j]);
+ }
+
+ //@todo not caring about globals/scopes right now
+ };
+
+ this.$updateVar = function(xmlVar, fVar){
+ xmlVar.setAttribute("value", this.$valueString(fVar.value));
+ xmlVar.setAttribute("type", fVar.value.type);
+ xmlVar.setAttribute("ref", fVar.value.ref);
+ apf.xmldb.setAttribute(xmlVar, "children", hasChildren[fVar.value.type] ? "true" : "false");
+ };
+
+ this.$buildFrame = function(frame, ref, xml){
+ var script = ref(frame.script.ref);
+ xml.push(
+ ""
+ );
+ xml.push("");
+
+ var receiver = {
+ name: "this",
+ value: frame.receiver
+ };
+ xml.push(this.$serializeVariable(receiver));
+
+ var j, l;
+ for (j = 0, l = frame.arguments.length; j < l; j++) {
+ if (frame.arguments[j].name)
+ xml.push(this.$serializeVariable(frame.arguments[j]));
+ }
+ for (j = 0, l = frame.locals.length; j < l; j++) {
+ if (frame.locals[j].name !== ".arguments")
+ xml.push(this.$serializeVariable(frame.locals[j]));
+ }
+ xml.push("");
+ xml.push("");
+
+ xml.push("");
+ var scopes = frame.scopes;
+ for (j = 0, l = scopes.length; j < l; j++) {
+ var scope = scopes[j];
+ xml.push("");
+ }
+ xml.push("");
+
+ xml.push("");
+ };
+
+ this.backtrace = function(model, callback) {
+ var _self = this;
+ this.$debugger.backtrace(null, null, null, true, function(body, refs) {
+ function ref(id) {
+ for (var i=0; i" + xml.join("") + "");
+ _self.setFrame(model.data.firstChild);
+ }
+ callback();
+ });
+ };
+
+ this.loadScript = function(script, callback) {
+ var id = script.getAttribute("scriptid");
+ var _self = this;
+ this.$debugger.scripts(4, [id], true, function(scripts) {
+ if (!scripts.length)
+ return;
+ var script = scripts[0];
+ callback(script.source);
+ });
+ };
+
+ this.loadObjects = function(item, callback) {
+ var ref = item.getAttribute("ref");
+ var _self = this;
+ this.$debugger.lookup([ref], false, function(body) {
+ var refs = [];
+ var props = body[ref].properties;
+ for (var i = 0, l = props.length; i < l; i++)
+ refs.push(props[i].ref);
+
+ _self.$debugger.lookup(refs, false, function(body) {
+ var xml = ["- "];
+ for (var i = 0, l = props.length; i < l; i++) {
+ props[i].value = body[props[i].ref];
+ xml.push(_self.$serializeVariable(props[i]));
+ }
+ xml.push("
");
+ callback(xml.join(""));
+ });
+ });
+ };
+
+ this.loadFrame = function(frame, callback) {
+ //var xml = " "
+ var scopes = frame.getElementsByTagName("scope");
+
+ var frameIndex = parseInt(frame.getAttribute("index"), 10);
+
+ var _self = this;
+ var processed = 0;
+ var expected = 0;
+ var xml = [""];
+
+ for (var i = 0, l = scopes.length; i < l; i++) {
+ var scope = scopes[i];
+ var type = parseInt(scope.getAttribute("type"), 10);
+
+ // ignore local and global scope
+ if (type > 1) {
+ expected += 1;
+ var index = parseInt(scope.getAttribute("index"), 10);
+ this.$debugger.scope(index, frameIndex, true, function(body) {
+ var props = body.object.properties;
+ for (j = 0, l2 = props.length; j < l2; j++)
+ xml.push(_self.$serializeVariable(props[j]));
+ processed += 1;
+ if (processed == expected) {
+ xml.push("");
+ callback(xml.join(""));
+ }
+ });
+ }
+ }
+ if (expected === 0)
+ return callback("");
+ };
+
+ this.setFrame = function(frame) {
+ this.$activeFrame = frame;
+ this.dispatchEvent("changeFrame", {data: frame});
+ };
+
+
+ this.getActiveFrame = function() {
+ return this.$activeFrame;
+ };
+
+ this.setBreakpoints = function(model, callback) {
+ var _self = this;
+
+ var breakpoints = model.queryNodes("breakpoint");
+ _self.$debugger.listbreakpoints(function(v8Breakpoints) {
+ if (v8Breakpoints.breakpoints) {
+ var bp;
+ for (var bId in _self.$breakpoints) {
+ bp = _self.$breakpoints[bId];
+ bp.destroy();
+ if (bp.xml) {
+ apf.xmldb.removeNode(bp.xml);
+ delete bp.xml;
+ }
+ }
+ _self.$breakpoints = {};
+
+ for (var i = 0, l = v8Breakpoints.breakpoints.length; i < l; i++) {
+ if (v8Breakpoints.breakpoints[i].type == "scriptId")
+ continue;
+
+ var breakpoint = Breakpoint.fromJson(v8Breakpoints.breakpoints[i], _self.$debugger);
+ var id = breakpoint.source + "|" + breakpoint.line;
+
+ _self.$breakpoints[id] = breakpoint;
+
+ model.removeXml("breakpoint[@script='" + breakpoint.source + "' and @line='" + breakpoint.line + "']");
+ breakpoint.xml = model.appendXml(_self.$getBreakpointXml(breakpoint, 0));
+ }
+ }
+
+ var modelBps = model.queryNodes("breakpoint") || [];
+ apf.asyncForEach(Array.prototype.slice.call(modelBps, 0), function(modelBp, next) {
+ var script = modelBp.getAttribute("script");
+ var line = modelBp.getAttribute("line");
+ var id = script + "|" + line;
+ var bp = _self.$breakpoints[id];
+
+ if (modelBp.parentNode)
+ apf.xmldb.removeNode(modelBp);
+ if (!bp) {
+ bp = _self.$addBreakpoint({
+ id: id,
+ name: script,
+ row: line,
+ col: modelBp.getAttribute("column"),
+ lineOffset: 0,
+ scriptId: null,
+ data: {
+ condition: modelBp.getAttribute("condition"),
+ ignoreCount: parseInt(modelBp.getAttribute("ignorecount") || 0, 10),
+ enabled: (modelBp.getAttribute("enabled") == "true")
+ }
+ }, model, next);
+ }
+ else {
+ model.appendXml(_self.$getBreakpointXml(bp, 0));
+ next();
+ }
+ }, callback);
+ });
+ };
+
+ this.toggleBreakpoint = function(script, relativeRow, model) {
+ var name = script.getAttribute("scriptname");
+ var lineOffset = parseInt(script.getAttribute("lineoffset") || "0", 10);
+ var row = lineOffset + relativeRow;
+ var id = name + "|" + row;
+ var breakpoint = this.$breakpoints[id];
+ var _self = this;
+
+ if (breakpoint) {
+ try {
+ breakpoint.clear(function() {
+ _self.$removeBreakpoint(breakpoint, model);
+ });
+ }
+ catch(ex) {
+ // aie! failed, remove it to be sure.
+ _self.$removeBreakpoint(breakpoint, model);
+ }
+ }
+ else {
+ breakpoint = this.$addBreakpoint({
+ id: id,
+ name: name,
+ row: row,
+ col: 0,
+ lineOffset: lineOffset,
+ scriptId: script.getAttribute("scriptid")
+ }, model);
+ }
+ };
+
+ this.$removeBreakpoint = function(bp, model) {
+ if (bp.$id) {
+ var node,
+ xpath = "breakpoint[@id=" + bp.$id + "]";
+ while (node = model.queryNode(xpath))
+ apf.xmldb.removeNode(node);
+ }
+ delete this.$breakpoints[bp.id];
+ };
+
+ this.$addBreakpoint = function(options, model, callback) {
+ //id, name, row, col, lineOffset, scriptId, dbg, data
+ var bp = this.$breakpoints[options.id] = new Breakpoint(options.name, options.row, options.col, options.dbg);
+ bp.id = options.id;
+ if (options.data)
+ apf.extend(bp, options.data);
+ var _self = this;
+ bp.attach(this.$debugger, function() {
+ // TODO: error handling
+ model.appendXml(_self.$getBreakpointXml(bp, options.lineOffset, options.scriptId));
+ callback && callback();
+ });
+ return bp;
+ };
+
+ this.$getBreakpointXml = function(breakpoint, lineOffset, scriptId) {
+ var xml = [];
+ xml.push(
+ ""
+ );
+
+ return xml.join("");
+ };
+
+ this.continueScript = function(callback) {
+ this.$debugger.continueScript(null, null, callback);
+ };
+
+ this.stepInto = function(callback) {
+ this.$debugger.continueScript("in", 1, callback);
+ };
+
+ this.stepNext = function(callback) {
+ this.$debugger.continueScript("next", 1, callback);
+ };
+
+ this.stepOut = function(callback) {
+ this.$debugger.continueScript("out", 1, callback);
+ };
+
+ this.suspend = function() {
+ this.$debugger.suspend();
+ };
+
+ this.changeLive = function(scriptId, newSource, previewOnly, callback) {
+ this.$debugger.changelive(scriptId, newSource, previewOnly, callback);
+ };
+
+ this.evaluate = function(expression, frame, global, disableBreak, callback){
+ this.$debugger.evaluate(expression, frame, global, disableBreak, function(body, refs, error){
+ var str = [];
+ var name = expression.trim();
+ if (error) {
+ str.push(" ");
+ }
+ else {
+ str.push(
+ " "
+ );
+ }
+ callback(apf.getXml(str.join("")), body, refs, error);
+ });
+ };
+
+ this.$valueString = function(value) {
+ switch (value.type) {
+ case "undefined":
+ case "null":
+ return value.type;
+
+ case "boolean":
+ case "number":
+ case "string":
+ return value.value + "";
+
+ case "object":
+ return "[" + value.className + "]";
+
+ case "function":
+ return "function " + value.inferredName + "()";
+
+ default:
+ return value.type;
+ };
+ };
+
+ this.$frameToString = function(frame) {
+ var str = [];
+ var args = frame.arguments;
+ var argsStr = [];
+
+ str.push(frame.func.name || frame.func.inferredName || "anonymous", "(");
+ for (var i = 0, l = args.length; i < l; i++) {
+ var arg = args[i];
+ if (!arg.name)
+ continue;
+ argsStr.push(arg.name);
+ }
+ str.push(argsStr.join(", "), ")");
+ return str.join("");
+ };
+
+ this.$serializeVariable = function(item, name) {
+ var str = [
+ " "
+ ];
+ return str.join("");
+ };
+
+}).call(V8Debugger.prototype = new apf.Class());
+
+});
+// #endif
diff --git a/client/apf/elements/dbg/v8debughost.js b/client/apf/elements/dbg/v8debughost.js
new file mode 100644
index 00000000000..5236565bc0f
--- /dev/null
+++ b/client/apf/elements/dbg/v8debughost.js
@@ -0,0 +1,89 @@
+// #ifdef __AMLDEBUGGER || __INC_ALL
+if (apf.hasRequireJS) define("apf/elements/dbg/v8debughost",
+ ["module",
+ "debug/StandaloneV8DebuggerService",
+ "debug/V8Debugger",
+ "apf/elements/dbg/v8debugger"],
+ function(module, StandaloneV8DebuggerService, V8Debugger, APFV8Debugger) {
+
+var V8DebugHost = module.exports = function(hostname, port, o3obj) {
+ this.$hostname = hostname;
+ this.$port = port;
+ this.$o3obj = o3obj;
+
+ this.$debugger = null;
+
+ this.$init();
+};
+
+(function() {
+
+ this.$connect = function(callback) {
+ var self = this;
+
+ if (this.state == "connected") {
+ return callback.call(this);
+ } else {
+ this.addEventListener("connect", function() {
+ self.removeEventListener("connect", arguments.callee);
+ callback.call(self);
+ });
+ }
+ if (this.state == "connecting")
+ return;
+
+ this.state = "connecting";
+
+ var socket = this.$socket = new O3Socket(this.$hostname, this.$port, this.$o3obj);
+ this.$v8ds = new StandaloneV8DebuggerService(socket);
+
+ this.state = "connected";
+ this.dispatchEvent("connect");
+
+ window.onunload = this.disconnect.bind(this);
+ callback.call(this);
+ };
+
+ this.loadTabs = function(model) {
+ model.load("V8");
+ };
+
+ this.attach = function(tabId, callback) {
+ var dbg = this.$debugger;
+
+ if (dbg)
+ return callback(null, dbg)
+
+ var self = this;
+ this.$connect(function() {
+ self.$v8ds.attach(0, function() {
+ dbg = new APFV8Debugger(new V8Debugger(0, self.$v8ds), this);
+ self.$debugger = dbg;
+ callback(null, dbg);
+ });
+ });
+ };
+
+ this.detach = function(dbg, callback) {
+ if (!dbg || this.$debugger !== dbg)
+ return callback();
+
+ this.$debugger = null;
+
+ var self = this;
+ this.$v8ds.detach(0, function(err) {
+ dbg.dispatchEvent("detach");
+ self.$socket.close();
+ self.dispatchEvent("disconnect", {});
+ callback && callback(err);
+ });
+ };
+
+ this.disconnect = function(callback) {
+ this.detach(this.$debugger, callback);
+ };
+
+}).call(V8DebugHost.prototype = new apf.Class());
+
+});
+// #endif
\ No newline at end of file
diff --git a/client/apf/elements/dbg/v8websocketdebughost.js b/client/apf/elements/dbg/v8websocketdebughost.js
new file mode 100644
index 00000000000..a9189ead9fc
--- /dev/null
+++ b/client/apf/elements/dbg/v8websocketdebughost.js
@@ -0,0 +1,68 @@
+// #ifdef __AMLDEBUGGER || __INC_ALL
+if (apf.hasRequireJS) define("apf/elements/dbg/v8websocketdebughost",
+ ["module",
+ "debug/WSV8DebuggerService",
+ "debug/V8Debugger",
+ "apf/elements/dbg/v8debugger"],
+ function(module, WSV8DebuggerService, V8Debugger, APFV8Debugger) {
+
+var V8WebSocketDebugHost = module.exports = function(socket) {
+ this.$socket = socket;
+ this.$debugger = null;
+
+ this.$init();
+};
+
+(function() {
+
+ this.$connect = function(callback) {
+ if (this.state != "connected")
+ this.$v8ds = new WSV8DebuggerService(this.$socket);
+
+ this.state = "connected";
+ this.dispatchEvent("connect");
+ callback.call(this);
+ };
+
+ this.loadTabs = function(model) {
+ model.load("V8");
+ };
+
+ this.attach = function(tabId, callback) {
+ var dbg = this.$debugger;
+
+ if (dbg)
+ return callback(null, dbg)
+
+ var self = this;
+ this.$connect(function() {
+ self.$v8ds.attach(0, function() {
+ dbg = new APFV8Debugger(new V8Debugger(0, self.$v8ds), this);
+ self.$debugger = dbg;
+ callback(null, dbg);
+ });
+ });
+ };
+
+ this.detach = function(dbg, callback) {
+ if (!dbg || this.$debugger !== dbg)
+ return callback();
+
+ this.$debugger = null;
+
+ var self = this;
+ this.$v8ds.detach(0, function(err) {
+ dbg.dispatchEvent("detach");
+ self.dispatchEvent("disconnect", {});
+ callback && callback(err);
+ });
+ };
+
+ this.disconnect = function(callback) {
+ this.detach(this.$debugger, callback);
+ };
+
+}).call(V8WebSocketDebugHost.prototype = new apf.Class());
+
+});
+// #endif
diff --git a/client/apf/elements/debugger.js b/client/apf/elements/debugger.js
new file mode 100644
index 00000000000..9ec8a596e59
--- /dev/null
+++ b/client/apf/elements/debugger.js
@@ -0,0 +1,338 @@
+/*
+ * See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ *
+ */
+
+// #ifdef __AMLDEBUGGER || __INC_ALL
+if (apf.hasRequireJS) define("apf/elements/debugger",
+ ["module"], function(module) {
+
+apf.dbg = module.exports = function(struct, tagName){
+ this.$init(tagName || "debugger", apf.NODE_HIDDEN, struct);
+};
+
+(function(){
+
+ this.$host = null;
+ this.$debugger = null;
+
+ this.$supportedProperties.push("state-running", "state-attached",
+ "model-sources", "model-stacks", "model-breakpoints", "activeframe");
+
+ this.$createModelPropHandler = function(name, xml, callback) {
+ return function(value) {
+ if (!value) return;
+ //#ifdef __WITH_NAMESERVER
+ this[name] = apf.setReference(value,
+ apf.nameserver.register("model", value, new apf.model()));
+
+ // set the root node for this model
+ this[name].id = this[name].name = value;
+ this[name].load(xml);
+ //#endif
+ }
+ };
+
+ this.$createStatePropHandler = function(name) {
+ return function(value) {
+ if (!value) return;
+ //#ifdef __WITH_NAMESERVER
+ this[name] = apf.setReference(value,
+ apf.nameserver.register("state", value, new apf.state()));
+
+ // set the root node for this model
+ this[name].id = this[name].name = value;
+ this[name].deactivate();
+ //#endif
+ }
+ };
+
+ this.$propHandlers["model-sources"] = this.$createModelPropHandler("$mdlSources", "");
+ this.$propHandlers["model-stack"] = this.$createModelPropHandler("$mdlStack", "");
+ this.$propHandlers["model-breakpoints"] = this.$createModelPropHandler("$mdlBreakpoints", "");
+
+ this.$propHandlers["state-running"] = this.$createStatePropHandler("$stRunning");
+ this.$propHandlers["state-attached"] = this.$createStatePropHandler("$stAttached");
+
+ this.$propHandlers["activeframe"] = function(value) {
+ if (this.$debugger) {
+ this.$ignoreFrameEvent = true;
+ this.$debugger.setFrame(value);
+ this.$ignoreFrameEvent = false;
+ }
+ this.dispatchEvent("changeframe", {data: value});
+ };
+
+ this.attach = function(host, tab) {
+ var _self = this;
+
+ host.$attach(this, tab, function(err, dbgImpl) {
+ _self.$host = host;
+ _self.$debugger = dbgImpl;
+
+ _self.$loadSources(function() {
+ dbgImpl.setBreakpoints(_self.$mdlBreakpoints, function() {
+ var backtraceModel = new apf.model();
+ backtraceModel.load("");
+
+ _self.$debugger.backtrace(backtraceModel, function() {
+ var frame = backtraceModel.queryNode("frame[1]");
+
+ if (!_self.$allowAttaching(frame)) {
+ _self.$debugger.continueScript();
+ }
+ else {
+ _self.$mdlStack.load(backtraceModel.data);
+ }
+
+ dbgImpl.addEventListener("afterCompile", _self.$onAfterCompile.bind(_self));
+
+ _self.$stAttached.activate();
+ _self.$stRunning.setProperty("active", dbgImpl.isRunning());
+
+ dbgImpl.addEventListener("changeRunning", _self.$onChangeRunning.bind(_self));
+ dbgImpl.addEventListener("break", _self.$onBreak.bind(_self));
+ dbgImpl.addEventListener("detach", _self.$onDetach.bind(_self));
+ dbgImpl.addEventListener("changeFrame", _self.$onChangeFrame.bind(_self));
+ });
+ });
+ });
+ });
+ };
+
+ this.$allowAttaching = function (frame) {
+ var _self = this;
+
+ if (frame) {
+ var scriptId = frame.getAttribute("scriptid");
+ var scriptName = _self.$mdlSources.queryValue("file[@scriptid='" + scriptId + "']/@scriptname");
+
+ if (scriptName) {
+ var line = frame.getAttribute("line");
+ var bp = _self.$mdlBreakpoints.queryNode("breakpoint[@script='" + scriptName + "' and @line='" + line + "']");
+ }
+ if (!scriptName || !bp) {
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+ };
+
+ this.$onChangeRunning = function() {
+ var isRunning = this.$debugger && this.$debugger.isRunning();
+ if (this.$stRunning.active && !isRunning)
+ this.$onBreak();
+
+ this.$stRunning.setProperty("active", isRunning);
+
+ //if (isRunning)
+ //this.$mdlStack.load("");
+ };
+
+ this.$onBreak = function() {
+ var _self = this;
+ if (!this.$debugger || this.$debugger.isRunning())
+ return;
+
+ this.$debugger.backtrace(this.$mdlStack, function() {
+ _self.dispatchEvent("break");
+ });
+ };
+
+ this.$onAfterCompile = function(e) {
+ var id = e.script.getAttribute("id");
+ var oldNode = this.$mdlSources.queryNode("//file[@id='" + id + "']");
+ if (oldNode)
+ this.$mdlSources.removeXml(oldNode);
+ this.$mdlSources.appendXml(e.script);
+ };
+
+ this.$onDetach = function() {
+ if (this.$debugger) {
+ this.$debugger.destroy();
+ this.$debugger = null;
+ }
+
+ this.$host = null;
+
+ this.$mdlSources.load("");
+ this.$mdlStack.load("");
+ this.$stAttached.deactivate();
+ this.setProperty("activeframe", null);
+ };
+
+ this.$onChangeFrame = function() {
+ if (!this.$ignoreFrameEvent) {
+ this.setProperty("activeframe", this.$debugger.getActiveFrame());
+ }
+ };
+
+ this.changeFrame = function(frame) {
+ this.$debugger.setFrame(frame);
+ };
+
+ this.detach = function(callback) {
+ this.continueScript();
+ if (this.$host)
+ this.$host.$detach(this.$debugger, callback);
+ else
+ this.$onDetach();
+ };
+
+ this.$loadSources = function(callback) {
+ this.$debugger.scripts(this.$mdlSources, callback);
+ };
+
+ this.loadScript = function(script, callback) {
+ this.$debugger.loadScript(script, callback);
+ };
+
+ this.loadObjects = function(item, callback) {
+ this.$debugger.loadObjects(item, callback);
+ };
+
+ this.loadFrame = function(frame, callback) {
+ this.$debugger.loadFrame(frame, callback);
+ };
+
+ this.toggleBreakpoint = function(script, row) {
+ var model = this.$mdlBreakpoints;
+ if (this.$debugger) {
+ this.$debugger.toggleBreakpoint(script, row, model);
+ }
+ else {
+ var scriptName = script.getAttribute("scriptname");
+ var bp = model.queryNode("breakpoint[@script='" + scriptName + "' and @line='" + row + "']");
+ if (bp) {
+ apf.xmldb.removeNode(bp);
+ }
+ else {
+ // filename is something like blah/blah/workspace/realdir/file
+ // we are only interested in the part after workspace for display purposes
+ var tofind = "/workspace/";
+ var path = script.getAttribute("path");
+ var displayText = path;
+ if (path.indexOf(tofind) > -1) {
+ displayText = path.substring(path.indexOf(tofind) + tofind.length);
+ }
+
+ var bp = apf.n("")
+ .attr("script", scriptName)
+ .attr("line", row)
+ .attr("text", displayText + ":" + (parseInt(row, 10) + 1))
+ .attr("lineoffset", 0)
+ .node();
+ model.appendXml(bp);
+ }
+ }
+ };
+
+ this.continueScript = function(callback) {
+ this.dispatchEvent("beforecontinue");
+
+ if (this.$debugger)
+ this.$debugger.continueScript(callback);
+ else
+ callback && callback();
+ };
+
+ this.stepInto = function() {
+ this.dispatchEvent("beforecontinue");
+
+ this.$debugger && this.$debugger.stepInto();
+ };
+
+ this.stepNext = function() {
+ this.dispatchEvent("beforecontinue");
+
+ this.$debugger && this.$debugger.stepNext();
+ };
+
+ this.stepOut = function() {
+ this.dispatchEvent("beforecontinue");
+
+ this.$debugger && this.$debugger.stepOut();
+ };
+
+ this.suspend = function() {
+ this.$debugger && this.$debugger.suspend();
+ };
+
+ this.evaluate = function(expression, frame, global, disableBreak, callback){
+ this.$debugger && this.$debugger.evaluate(expression, frame, global, disableBreak, callback);
+ };
+
+ this.changeLive = function(scriptId, newSource, previewOnly, callback) {
+ this.$debugger && this.$debugger.changeLive(scriptId, newSource, previewOnly, callback);
+ };
+
+}).call(apf.dbg.prototype = new apf.AmlElement());
+
+apf.aml.setElement("debugger", apf.dbg);
+
+
+window.adbg = {
+ exec : function(method, args, callback, options) {
+ if (method == "loadScript") {
+ var dbg = args[0];
+ var script = args[1];
+ dbg.loadScript(script, function(source) {
+ if (options && options.callback) {
+ options.callback(apf.escapeXML(source), apf.SUCCESS);
+ } else {
+// callback("" + apf.escapeXML(source) + "", apf.SUCCESS);
+ //TODO: ugly text() bug workaround
+ callback("", "]] >") + "]]>", apf.SUCCESS);
+ }
+ });
+ }
+ else if (method == "loadObjects") {
+ var dbg = args[0];
+ var item = args[1];
+
+ dbg.loadObjects(item, function(xml) {
+ if (options && options.callback) {
+ options.callback(xml, apf.SUCCESS);
+ } else {
+ callback(xml, apf.SUCCESS);
+ }
+ });
+ }
+ else if (method == "loadFrame") {
+ var dbg = args[0];
+ var frame = args[1];
+
+ dbg.loadFrame(frame, function(xml) {
+ if (options && options.callback) {
+ options.callback(xml, apf.SUCCESS);
+ } else {
+ callback(xml, apf.SUCCESS);
+ }
+ });
+ }
+ }
+ };
+
+(apf.$asyncObjects || (apf.$asyncObjects = {}))["adbg"] = 1;
+
+});
+// #endif
diff --git a/client/apf/elements/debughost.js b/client/apf/elements/debughost.js
new file mode 100644
index 00000000000..0d0b4cb05e2
--- /dev/null
+++ b/client/apf/elements/debughost.js
@@ -0,0 +1,146 @@
+// #ifdef __AMLDEBUGGER || __INC_ALL
+if (apf.hasRequireJS) define("apf/elements/debughost",
+ ["module",
+ "apf/elements/dbg/chromedebughost",
+ "apf/elements/dbg/v8debughost",
+ "apf/elements/dbg/v8websocketdebughost"],
+ function(module, ChromeDebugHost, V8DebugHost, V8WebSocketDebugHost) {
+
+apf.debughost = module.exports = function(struct, tagName){
+ this.$init(tagName || "debughost", apf.NODE_HIDDEN, struct);
+};
+
+(function(){
+
+ this.port = 9222;
+ this.server = "localhost";
+ this.type = "chrome";
+ this.autoinit = false;
+ this.$modelTabs = null;
+ this.$stateConnected = null;
+
+ this.$host = null;
+
+ this.$booleanProperties["autostart"] = true;
+
+ this.$supportedProperties.push("port", "server", "type", "autoinit",
+ "model-tabs", "state-connected", "strip");
+
+ this.$propHandlers["model-tabs"] = function(value) {
+ if (!value) return;
+ //#ifdef __WITH_NAMESERVER
+ this.$modelTabs = apf.nameserver.get("model", value) ||
+ apf.setReference(value, apf.nameserver.register("model", value, new apf.model()));
+
+ // set the root node for this model
+ this.$modelTabs.id = this.$modelTabs.name = value;
+ this.$modelTabs.load("");
+ //#endif
+ };
+
+ this.$propHandlers["state-connected"] = function(value) {
+ if (!value) return;
+ //#ifdef __WITH_NAMESERVER
+ this.$stateConnected = apf.nameserver.get("state", value) ||
+ apf.setReference(value, apf.nameserver.register("state", value, new apf.state()));
+
+ // set the root node for this model
+ this.$stateConnected.id = this.$stateConnected.name = value;
+ this.$stateConnected.deactivate();
+ //#endif
+ };
+
+ this.init = function() {
+ if (this.$host)
+ return;
+
+ if (this.type == "chrome" || this.type == "v8" || this.type == "v8-ws") {
+ if (!apf.debughost.$o3obj && this.type !== "v8-ws") {
+ apf.debughost.$o3obj = window.o3Obj || o3.create("8A66ECAC-63FD-4AFA-9D42-3034D18C88F4", {
+ oninstallprompt: function() { alert("can't find o3 plugin"); },
+ product: "O3Demo"
+ });
+ }
+
+ if (this.type == "chrome") {
+ this.$host = new ChromeDebugHost(this.server, this.port, apf.debughost.$o3obj);
+ }
+ else if (this.type == "v8") {
+ this.$host = new V8DebugHost(this.server, this.port, apf.debughost.$o3obj);
+ }
+ else if (this.type == "v8-ws") {
+ var socket = this.dispatchEvent("socketfind");
+ if (!socket)
+ throw new Error("no socket found!")
+ this.$host = new V8WebSocketDebugHost(socket);
+ }
+ else if (this.type == "chrome-ws") {
+ var socket = this.dispatchEvent("socketfind");
+ if (!socket)
+ throw new Error("no socket found!")
+ this.$host = new ChromeDebugHost(null, null, null, socket);
+ }
+
+ var self = this;
+ this.$host.addEventListener("connect", function() {
+ self.dispatchEvent("connect");
+ self.$stateConnected.activate();
+ });
+ this.$host.addEventListener("disconnect", function() {
+ self.dispatchEvent("disconnect");
+ self.$stateConnected.deactivate();
+ });
+
+ this.loadTabs();
+ }
+ };
+
+ this.loadTabs = function() {
+ if (!this.$host)
+ this.init();
+
+ var self = this;
+ this.$host.loadTabs(this.$modelTabs, function() {
+ self.$dispatchEvent("tabsloaded");
+ });
+ }
+
+ this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
+ if (this.autoinit)
+ this.init();
+ });
+
+ this.$attach = function(dbg, tab, callback) {
+ if (!this.$host)
+ this.init();
+
+ var id = tab ? tab.getAttribute("id") : null;
+
+ var _self = this;
+ this.$host.attach(id, function(err, dbg) {
+ dbg.setStrip(_self.strip || "");
+ callback(err, dbg);
+ });
+ };
+
+ this.$detach = function(dbgImpl, callback) {
+ if (!this.$host)
+ return;
+
+ this.$host.detach(dbgImpl, callback);
+ };
+
+ this.disconnect = function() {
+ if (!this.$host)
+ return;
+
+ this.$host.disconnect();
+ this.$host = null;
+ };
+
+}).call(apf.debughost.prototype = new apf.AmlElement());
+
+apf.aml.setElement("debughost", apf.debughost);
+
+});
+// #endif
diff --git a/client/ext/code/code.js b/client/ext/code/code.js
index c75858f0d36..9712be95b5f 100644
--- a/client/ext/code/code.js
+++ b/client/ext/code/code.js
@@ -7,6 +7,8 @@
define(function(require, exports, module) {
+require("apf/elements/codeeditor");
+
var ide = require("core/ide");
var ext = require("core/ext");
var EditSession = require("ace/edit_session").EditSession;
diff --git a/client/ext/debugger/debugger.js b/client/ext/debugger/debugger.js
index 7e4099ecda2..1c345b576da 100644
--- a/client/ext/debugger/debugger.js
+++ b/client/ext/debugger/debugger.js
@@ -7,6 +7,8 @@
define(function(require, exports, module) {
+require("apf/elements/codeeditor");
+
var ide = require("core/ide");
var ext = require("core/ext");
var editors = require("ext/editors/editors");
diff --git a/client/ext/jslanguage/narcissus_jshint.js b/client/ext/jslanguage/narcissus_jshint.js
index 8de6bb0c1c5..5c7f34896f8 100644
--- a/client/ext/jslanguage/narcissus_jshint.js
+++ b/client/ext/jslanguage/narcissus_jshint.js
@@ -49,7 +49,10 @@ handler.analyze = function(doc) {
lint(value, {
undef: false,
onevar: false,
- passfail: false
+ passfail: false,
+ devel: true,
+ browser: true,
+ node: true
});
lint.errors.forEach(function(warning) {
if (!warning)
diff --git a/client/ext/jslanguage/scope_analyzer.js b/client/ext/jslanguage/scope_analyzer.js
index fc6d3331ccf..2e0eb2caaf2 100644
--- a/client/ext/jslanguage/scope_analyzer.js
+++ b/client/ext/jslanguage/scope_analyzer.js
@@ -18,6 +18,262 @@ var baseLanguageHandler = require('ext/language/base_handler');
require('treehugger/traverse');
var handler = module.exports = Object.create(baseLanguageHandler);
+// Based on https://github.com/jshint/jshint/blob/master/jshint.js#L331
+var GLOBALS = {
+ // Literals
+ "true" : true,
+ "false" : true,
+ "undefined" : true,
+ "null" : true,
+ "this" : true,
+ "arguments" : true,
+ // Browser
+ ArrayBuffer : true,
+ ArrayBufferView : true,
+ Audio : true,
+ addEventListener : true,
+ applicationCache : true,
+ blur : true,
+ clearInterval : true,
+ clearTimeout : true,
+ close : true,
+ closed : true,
+ DataView : true,
+ defaultStatus : true,
+ document : true,
+ event : true,
+ FileReader : true,
+ Float32Array : true,
+ Float64Array : true,
+ FormData : true,
+ getComputedStyle : true,
+ HTMLElement : true,
+ HTMLAnchorElement : true,
+ HTMLBaseElement : true,
+ HTMLBlockquoteElement : true,
+ HTMLBodyElement : true,
+ HTMLBRElement : true,
+ HTMLButtonElement : true,
+ HTMLCanvasElement : true,
+ HTMLDirectoryElement : true,
+ HTMLDivElement : true,
+ HTMLDListElement : true,
+ HTMLFieldSetElement : true,
+ HTMLFontElement : true,
+ HTMLFormElement : true,
+ HTMLFrameElement : true,
+ HTMLFrameSetElement : true,
+ HTMLHeadElement : true,
+ HTMLHeadingElement : true,
+ HTMLHRElement : true,
+ HTMLHtmlElement : true,
+ HTMLIFrameElement : true,
+ HTMLImageElement : true,
+ HTMLInputElement : true,
+ HTMLIsIndexElement : true,
+ HTMLLabelElement : true,
+ HTMLLayerElement : true,
+ HTMLLegendElement : true,
+ HTMLLIElement : true,
+ HTMLLinkElement : true,
+ HTMLMapElement : true,
+ HTMLMenuElement : true,
+ HTMLMetaElement : true,
+ HTMLModElement : true,
+ HTMLObjectElement : true,
+ HTMLOListElement : true,
+ HTMLOptGroupElement : true,
+ HTMLOptionElement : true,
+ HTMLParagraphElement : true,
+ HTMLParamElement : true,
+ HTMLPreElement : true,
+ HTMLQuoteElement : true,
+ HTMLScriptElement : true,
+ HTMLSelectElement : true,
+ HTMLStyleElement : true,
+ HTMLTableCaptionElement : true,
+ HTMLTableCellElement : true,
+ HTMLTableColElement : true,
+ HTMLTableElement : true,
+ HTMLTableRowElement : true,
+ HTMLTableSectionElement : true,
+ HTMLTextAreaElement : true,
+ HTMLTitleElement : true,
+ HTMLUListElement : true,
+ HTMLVideoElement : true,
+ Int16Array : true,
+ Int32Array : true,
+ Int8Array : true,
+ Image : true,
+ localStorage : true,
+ location : true,
+ navigator : true,
+ open : true,
+ openDatabase : true,
+ Option : true,
+ parent : true,
+ print : true,
+ removeEventListener : true,
+ resizeBy : true,
+ resizeTo : true,
+ screen : true,
+ scroll : true,
+ scrollBy : true,
+ scrollTo : true,
+ sessionStorage : true,
+ setInterval : true,
+ setTimeout : true,
+ SharedWorker : true,
+ Uint16Array : true,
+ Uint32Array : true,
+ Uint8Array : true,
+ WebSocket : true,
+ window : true,
+ Worker : true,
+ XMLHttpRequest : true,
+ XPathEvaluator : true,
+ XPathException : true,
+ XPathExpression : true,
+ XPathNamespace : true,
+ XPathNSResolver : true,
+ XPathResult : true,
+ // Devel
+ alert : true,
+ confirm : true,
+ console : true,
+ Debug : true,
+ opera : true,
+ prompt : true,
+ // Frameworks
+ jQuery : true,
+ "$" : true,
+ "$$" : true,
+ goog : true,
+ dojo : true,
+ dojox : true,
+ dijit : true,
+ apf : true,
+ // mootools
+ Assets : true,
+ Browser : true,
+ Chain : true,
+ Class : true,
+ Color : true,
+ Cookie : true,
+ Core : true,
+ Document : true,
+ DomReady : true,
+ DOMReady : true,
+ Drag : true,
+ Element : true,
+ Elements : true,
+ Event : true,
+ Events : true,
+ Fx : true,
+ Group : true,
+ Hash : true,
+ HtmlTable : true,
+ Iframe : true,
+ IframeShim : true,
+ InputValidator : true,
+ instanceOf : true,
+ Keyboard : true,
+ Locale : true,
+ Mask : true,
+ MooTools : true,
+ Native : true,
+ Options : true,
+ OverText : true,
+ Request : true,
+ Scroller : true,
+ Slick : true,
+ Slider : true,
+ Sortables : true,
+ Spinner : true,
+ Swiff : true,
+ Tips : true,
+ Type : true,
+ typeOf : true,
+ URI : true,
+ Window : true,
+ // prototype.js
+ '$A' : true,
+ '$F' : true,
+ '$H' : true,
+ '$R' : true,
+ '$break' : true,
+ '$continue' : true,
+ '$w' : true,
+ Abstract : true,
+ Ajax : true,
+ Enumerable : true,
+ Field : true,
+ Form : true,
+ Insertion : true,
+ ObjectRange : true,
+ PeriodicalExecuter : true,
+ Position : true,
+ Prototype : true,
+ Selector : true,
+ Template : true,
+ Toggle : true,
+ Try : true,
+ Autocompleter : true,
+ Builder : true,
+ Control : true,
+ Draggable : true,
+ Draggables : true,
+ Droppables : true,
+ Effect : true,
+ Sortable : true,
+ SortableObserver : true,
+ Sound : true,
+ Scriptaculous : true,
+ // require.js
+ define : true,
+ // node.js
+ __filename : true,
+ __dirname : true,
+ Buffer : true,
+ exports : true,
+ GLOBAL : true,
+ global : true,
+ module : true,
+ process : true,
+ require : true,
+ // Standard
+ Array : true,
+ Boolean : true,
+ Date : true,
+ decodeURI : true,
+ decodeURIComponent : true,
+ encodeURI : true,
+ encodeURIComponent : true,
+ Error : true,
+ 'eval' : true,
+ EvalError : true,
+ Function : true,
+ hasOwnProperty : true,
+ isFinite : true,
+ isNaN : true,
+ JSON : true,
+ Math : true,
+ Number : true,
+ Object : true,
+ parseInt : true,
+ parseFloat : true,
+ RangeError : true,
+ ReferenceError : true,
+ RegExp : true,
+ String : true,
+ SyntaxError : true,
+ TypeError : true,
+ URIError : true,
+ // non-standard
+ escape : true,
+ unescape : true
+};
+
handler.handlesLanguage = function(language) {
return language === 'javascript';
};
@@ -76,76 +332,96 @@ handler.analyze = function(doc, ast) {
function scopeAnalyzer(scope, node, parentLocalVars) {
preDeclareHoisted(scope, node);
var localVariables = parentLocalVars || [];
- node.traverseTopDown(
- 'VarDecl(x)', function(b) {
- localVariables.push(scope[b.x.value]);
- },
- 'VarDeclInit(x, _)', function(b) {
- localVariables.push(scope[b.x.value]);
- },
- 'Assign(Var(x), _)', function(b, node) {
- if(!scope[b.x.value]) {
+ function analyze(scope, node) {
+ node.traverseTopDown(
+ 'VarDecl(x)', function(b) {
+ localVariables.push(scope[b.x.value]);
+ },
+ 'VarDeclInit(x, _)', function(b) {
+ localVariables.push(scope[b.x.value]);
+ },
+ 'Assign(Var(x), e)', function(b, node) {
+ if(!scope[b.x.value]) {
+ markers.push({
+ pos: node[0].getPos(),
+ type: 'warning',
+ message: 'Assigning to undeclared variable.'
+ });
+ }
+ else {
+ scope[b.x.value].addUse(node);
+ }
+ analyze(scope, b.e);
+ return this;
+ },
+ 'ForIn(Var(x), e, stats)', function(b, node) {
+ if(!scope[b.x.value]) {
+ markers.push({
+ pos: node[0].getPos(),
+ type: 'warning',
+ message: 'Using undeclared variable as iterator variable.'
+ });
+ }
+ else {
+ scope[b.x.value].addUse(node);
+ }
+ analyze(scope, b.e);
+ analyze(scope, b.stats);
+ return this;
+ },
+ 'Var(x)', function(b, node) {
+ node.setAnnotation("scope", scope);
+ if(scope[b.x.value]) {
+ scope[b.x.value].addUse(node);
+ } else if(handler.isFeatureEnabled("undeclaredVars") && !GLOBALS[b.x.value]) {
+ markers.push({
+ pos: this.getPos(),
+ type: 'warning',
+ message: "Undeclared variable."
+ });
+ }
+ return node;
+ },
+ 'Function(x, fargs, body)', function(b, node) {
+ node.setAnnotation("scope", scope);
+
+ var newScope = Object.create(scope);
+ newScope['this'] = new Variable();
+ b.fargs.forEach(function(farg) {
+ farg.setAnnotation("scope", newScope);
+ newScope[farg[0].value] = new Variable(farg);
+ if (handler.isFeatureEnabled("unusedFunctionArgs"))
+ localVariables.push(newScope[farg[0].value]);
+ });
+ scopeAnalyzer(newScope, b.body);
+ return node;
+ },
+ 'Catch(x, body)', function(b, node) {
+ var oldVar = scope[b.x.value];
+ // Temporarily override
+ scope[b.x.value] = new Variable(b.x);
+ scopeAnalyzer(scope, b.body, localVariables);
+ // Put back
+ scope[b.x.value] = oldVar;
+ return node;
+ },
+ 'PropAccess(_, "lenght")', function(b, node) {
markers.push({
- pos: node[0].getPos(),
+ pos: node.getPos(),
type: 'warning',
- message: 'Assigning to undeclared variable.'
+ message: "Did you mean 'length'?"
});
- }
- },
- 'ForIn(Var(x), _, _)', function(b, node) {
- if(!scope[b.x.value]) {
+ },
+ 'Call(Var("parseInt"), [_])', function() {
markers.push({
- pos: node[0].getPos(),
+ pos: this[0].getPos(),
type: 'warning',
- message: 'Using undeclared variable as iterator variable.'
+ message: "Missing radix argument."
});
}
- },
- 'Var(x)', function(b, node) {
- node.setAnnotation("scope", scope);
- if(scope[b.x.value]) {
- scope[b.x.value].addUse(node);
- }
- return node;
- },
- 'Function(x, fargs, body)', function(b, node) {
- node.setAnnotation("scope", scope);
-
- var newScope = Object.create(scope);
- newScope['this'] = new Variable();
- b.fargs.forEach(function(farg) {
- farg.setAnnotation("scope", newScope);
- newScope[farg[0].value] = new Variable(farg);
- if (handler.isFeatureEnabled("unusedFunctionArgs"))
- localVariables.push(newScope[farg[0].value]);
- });
- scopeAnalyzer(newScope, b.body);
- return node;
- },
- 'Catch(x, body)', function(b, node) {
- var oldVar = scope[b.x.value];
- // Temporarily override
- scope[b.x.value] = new Variable(b.x);
- scopeAnalyzer(scope, b.body, localVariables);
- // Put back
- scope[b.x.value] = oldVar;
- return node;
- },
- 'PropAccess(_, "lenght")', function(b, node) {
- markers.push({
- pos: node.getPos(),
- type: 'warning',
- message: "Did you mean 'length'?"
- });
- },
- 'Call(Var("parseInt"), [_])', function() {
- markers.push({
- pos: this[0].getPos(),
- type: 'warning',
- message: "Missing radix argument."
- });
- }
- );
+ );
+ }
+ analyze(scope, node);
if(!parentLocalVars) {
for (var i = 0; i < localVariables.length; i++) {
if (localVariables[i].uses.length === 0) {
diff --git a/client/ext/language/language.js b/client/ext/language/language.js
index bbdab5f211b..0c8d1f26add 100644
--- a/client/ext/language/language.js
+++ b/client/ext/language/language.js
@@ -9,6 +9,7 @@ define(function(require, exports, module) {
var ext = require("core/ext");
var ide = require("core/ide");
var editors = require("ext/editors/editors");
+var noderunner = require("ext/noderunner/noderunner");
var WorkerClient = require("ace/worker/worker_client").WorkerClient;
var complete = require('ext/language/complete');
@@ -28,7 +29,7 @@ module.exports = ext.register("ext/language/language", {
name : "Javascript Outline",
dev : "Ajax.org",
type : ext.GENERAL,
- deps : [editors],
+ deps : [editors, noderunner],
nodes : [],
alone : true,
markup : markup,
@@ -96,6 +97,7 @@ module.exports = ext.register("ext/language/language", {
this.setJSHint();
this.setInstanceHighlight();
this.setUnusedFunctionArgs();
+ this.setUndeclaredVars();
this.editor.on("changeSession", function(event) {
// Time out a litle, to let the page path be updated
@@ -122,10 +124,10 @@ module.exports = ext.register("ext/language/language", {
},
setPath: function() {
- var currentPath = tabEditors.getPage().getAttribute("id");
// Currently no code editor active
- if(!editors.currentEditor.ceEditor)
+ if(!editors.currentEditor.ceEditor || !tabEditors.getPage())
return;
+ var currentPath = tabEditors.getPage().getAttribute("id");
this.worker.call("switchFile", [currentPath, editors.currentEditor.ceEditor.syntax, this.editor.getSession().getValue(), this.editor.getCursorPosition()]);
},
@@ -147,7 +149,7 @@ module.exports = ext.register("ext/language/language", {
this.worker.emit("cursormove", {data: cursorPos});
},
- setUnusedFunctionArgs: function(yeah) {
+ setUnusedFunctionArgs: function() {
if(extSettings.model.queryValue("language/@unusedFunctionArgs") != "false")
this.worker.call("enableFeature", ["unusedFunctionArgs"]);
else
@@ -155,6 +157,14 @@ module.exports = ext.register("ext/language/language", {
this.setPath();
},
+ setUndeclaredVars: function() {
+ if(extSettings.model.queryValue("language/@undeclaredVars") != "false")
+ this.worker.call("enableFeature", ["undeclaredVars"]);
+ else
+ this.worker.call("disableFeature", ["undeclaredVars"]);
+ this.setPath();
+ },
+
/**
* Method attached to key combo for complete
*/
diff --git a/client/ext/language/liveinspect.js b/client/ext/language/liveinspect.js
index 5037912f3c6..84e74a3eea5 100644
--- a/client/ext/language/liveinspect.js
+++ b/client/ext/language/liveinspect.js
@@ -18,9 +18,21 @@ module.exports = (function () {
// get respective HTML elements
windowHtml = winLiveInspect.$ext;
datagridHtml = dgLiveInspect.$ext;
+ winLiveInspect.addEventListener("prop.visible", function(e) {
+ // don't track when hiding the window
+ if (!e.value)
+ return;
+ ide.dispatchEvent("track_action", {
+ type: "live inspect code",
+ expression: currentExpression || "no expression available yet."
+ });
+ });
};
var hook = function(_ext, worker) {
+ if (typeof stRunning === "undefined")
+ return;
+
ext.initExtension(this);
// listen to the worker's response
@@ -55,17 +67,17 @@ module.exports = (function () {
stDebugProcessRunning.addEventListener("prop.active", checkDebuggerActive);
// when hovering over the inspector window we should ignore all further listeners
- datagridHtml.addEventListener("mouseover", function () {
+ apf.addListener(datagridHtml, "mouseover", function() {
if (activeTimeout) {
clearTimeout(activeTimeout);
}
});
// we should track mouse movement over the whole window
- document.addEventListener("mousemove", onDocumentMouseMove)
+ apf.addListener(document, "mousemove", onDocumentMouseMove);
- // yes, this is superhacky but the editor function in APF is crazy
- datagridHtml.addEventListener("dblclick", initializeEditor);
+ // yes, this is superhacky but the editor function in APF is crazy
+ apf.addListener(datagridHtml, "dblclick", initializeEditor);
// when collapsing or expanding the datagrid we want to resize
dgLiveInspect.addEventListener("expand", resizeWindow);
@@ -171,10 +183,10 @@ module.exports = (function () {
};
// when blurring, update
- edit.addEventListener("blur", onBlur);
+ apf.addListener(edit, "blur", onBlur);
// on keydown, same same
- edit.addEventListener("keydown", function (ev) {
+ apf.addListener(edit, "keydown", function(ev) {
if (ev.keyCode === 27 || ev.keyCode === 13) { // tab or enter
return onBlur.call(this);
}
@@ -235,23 +247,30 @@ module.exports = (function () {
return;
}
- // see whether we hover over the editor
- if (ceEditor) {
- // calculate position
- var ele = ceEditor.$ext;
- var position = apf.getAbsolutePosition(ele, document.body);
- var left = position[0];
- var top = position[1];
-
- // x boundaries
- if (ev.pageX >= left && ev.pageX <= (left + ele.offsetWidth)) {
- // y boundaries
- if (ev.pageY >= top && ev.pageY <= (top + ele.offsetHeight)) {
- // we are in the editor, so return; this will be handled
- return;
- }
- }
- }
+ // see whether we hover over the editor or the quickwatch window
+ var mouseMoveAllowed = false;
+
+ var eles = [ ceEditor, winLiveInspect ];
+ // only the visible ones
+ eles.filter(function (ele) { return ele.visible; })
+ .map(function (ele) { return ele.$ext; }) // then get the HTML counterpart
+ .forEach(function (ele) {
+ // then detect real position
+ var position = apf.getAbsolutePosition(ele, document.body);
+ var left = position[0];
+ var top = position[1];
+
+ // x boundaries
+ if (ev.pageX >= left && ev.pageX <= (left + ele.offsetWidth)) {
+ // y boundaries
+ if (ev.pageY >= top && ev.pageY <= (top + ele.offsetHeight)) {
+ // we are in the editor, so return; this will be handled
+ mouseMoveAllowed = true;
+ }
+ }
+ });
+
+ if (mouseMoveAllowed) return;
// not in the editor?
if (winLiveInspect.visible) {
diff --git a/client/ext/language/settings.xml b/client/ext/language/settings.xml
index ccae5ca4a63..bba5a7dd031 100644
--- a/client/ext/language/settings.xml
+++ b/client/ext/language/settings.xml
@@ -8,6 +8,9 @@
+
diff --git a/client/ext/noderunner/noderunner.js b/client/ext/noderunner/noderunner.js
index 06d885252fa..b07904fa7ee 100644
--- a/client/ext/noderunner/noderunner.js
+++ b/client/ext/noderunner/noderunner.js
@@ -7,6 +7,9 @@
define(function(require, exports, module) {
+require("apf/elements/debugger");
+require("apf/elements/debughost");
+
var ide = require("core/ide");
var ext = require("core/ext");
var util = require("core/util");
diff --git a/client/ext/openfiles/openfiles.js b/client/ext/openfiles/openfiles.js
index 0f1f877455a..62575ba2a12 100644
--- a/client/ext/openfiles/openfiles.js
+++ b/client/ext/openfiles/openfiles.js
@@ -96,7 +96,7 @@ module.exports = ext.register("ext/openfiles/openfiles", {
tabEditors.addEventListener("afterswitch", function(e){
var page = e.nextPage;
- if (page) {
+ if (page && page.$model.data) {
var node = _self.model.queryNode("//node()[@path='" + page.$model.data.getAttribute("path") + "']");
if (node)
lstOpenFiles.select(node);
diff --git a/client/ext/save/save.js b/client/ext/save/save.js
index e6041841063..991ac66de87 100644
--- a/client/ext/save/save.js
+++ b/client/ext/save/save.js
@@ -42,8 +42,9 @@ module.exports = ext.register("ext/save/save", {
var at = e.page.$at;
if (!at.undo_ptr)
at.undo_ptr = at.$undostack[0];
- if (at.undo_ptr && at.$undostack[at.$undostack.length-1] !== at.undo_ptr
- || !at.undo_ptr && e.page.$doc.getNode().getAttribute("changed") == 1
+ var node = e.page.$doc.getNode();
+ if (node && at.undo_ptr && at.$undostack[at.$undostack.length-1] !== at.undo_ptr
+ || !at.undo_ptr && node.getAttribute("changed") == 1
&& e.page.$doc.getValue()) {
ext.initExtension(_self);
@@ -273,7 +274,11 @@ module.exports = ext.register("ext/save/save", {
panel.setAttribute("caption", "Saved file " + path);
ide.dispatchEvent("afterfilesave", {node: node, doc: doc, value: value});
- ide.dispatchEvent("track_action", {type: "save as filetype", fileType: node.getAttribute("name").split(".").pop()});
+ ide.dispatchEvent("track_action", {
+ type: "save as filetype",
+ fileType: node.getAttribute("name").split(".").pop(),
+ success: state != apf.SUCCESS ? "false" : "true"
+ });
apf.xmldb.removeAttribute(node, "saving");
apf.xmldb.removeAttribute(node, "new");
diff --git a/client/ext/settings/settings.js b/client/ext/settings/settings.js
index cdd34b827fe..1bde1da1e4f 100644
--- a/client/ext/settings/settings.js
+++ b/client/ext/settings/settings.js
@@ -39,11 +39,16 @@ module.exports = ext.register("ext/settings/settings", {
},
saveToFile : function() {
+ var data = this.model.data && apf.xmldb.cleanXml(this.model.data.xml) || "";
ide.send(JSON.stringify({
command: "settings",
action: "set",
- settings: this.model.data && apf.xmldb.cleanXml(this.model.data.xml) || ""
+ settings: data
}));
+ ide.dispatchEvent("track_action", {
+ type: "save settings",
+ settings: data
+ });
},
saveSettingsPanel: function() {
diff --git a/client/ext/tree/tree.js b/client/ext/tree/tree.js
index 06abda67c26..62160c2a4f0 100644
--- a/client/ext/tree/tree.js
+++ b/client/ext/tree/tree.js
@@ -188,7 +188,11 @@ module.exports = ext.register("ext/tree/tree", {
})
}
}));
- davProject.setAttribute("showhidden", "[{require('ext/settings/settings').model}::auto/tree/@showhidden]");
+
+ ide.addEventListener("loadsettings", function(e) {
+ var model = e.model;
+ (davProject.realWebdav || davProject).setAttribute("showhidden", model.queryValue('auto/tree/@showhidden'));
+ });
mnuView.appendChild(new apf.divider());
diff --git a/client/style/skins.xml b/client/style/skins.xml
index 4ea688e1687..714b4e2306b 100644
--- a/client/style/skins.xml
+++ b/client/style/skins.xml
@@ -2048,11 +2048,9 @@
}
.ace_gutter {
- position: absolute;
overflow-x: scroll;
overflow-y: hidden;
height: 100%;
- width: 60px !important;
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
diff --git a/server/cloud9/ide.js b/server/cloud9/ide.js
index 1c55ea3148a..3814ceeeb73 100644
--- a/server/cloud9/ide.js
+++ b/server/cloud9/ide.js
@@ -28,7 +28,6 @@ var Ide = module.exports = function(options, httpServer, exts, socket) {
paths: {
"ace": staticUrl + "/support/ace/lib/ace",
"debug": staticUrl + "/support/lib-v8debug/lib/v8debug",
- "apf": staticUrl + "/support/apf",
"treehugger": staticUrl + "/support/treehugger/lib/treehugger"
},
waitSeconds: 30
diff --git a/support/ace b/support/ace
index e1b21b0dc80..b2162f04083 160000
--- a/support/ace
+++ b/support/ace
@@ -1 +1 @@
-Subproject commit e1b21b0dc80edfaab789ffd7e81396521cf5e81b
+Subproject commit b2162f040835834b145b947c47b6abbb826511b3
diff --git a/support/apf b/support/apf
index e68111ecae1..1f3030695d0 160000
--- a/support/apf
+++ b/support/apf
@@ -1 +1 @@
-Subproject commit e68111ecae1918704cf302356acc32068c6f4e9d
+Subproject commit 1f3030695d05bd5418a9368d0c5b9eb4cfdb2b0e