diff --git a/configs/cli.js b/configs/cli.js index 8d95eff0..d6f247e1 100644 --- a/configs/cli.js +++ b/configs/cli.js @@ -56,7 +56,8 @@ return [ packagePath: "./c9.ide.auth/auth", accessToken: "token", ideBaseUrl: "", - apiUrl: APIURL + apiUrl: APIURL, + cli: true // userId: process.env.C9_USER }, { @@ -119,7 +120,8 @@ return [ local: true, home: process.env.HOME, setStatus: function(){}, - location: "" + location: "", + platform: process.platform, }, error_handler: { log: function(){} diff --git a/configs/client-default.js b/configs/client-default.js index 789b64f7..6f6d5192 100644 --- a/configs/client-default.js +++ b/configs/client-default.js @@ -98,7 +98,7 @@ module.exports = function(options) { packagePath: "plugins/c9.ide.plugins/debug" }, { - packagePath: "plugins/c9.ide.plugins/market" + packagePath: "plugins/c9.ide.plugins/packages" }, { packagePath: "plugins/c9.ide.plugins/test", @@ -456,7 +456,7 @@ module.exports = function(options) { { packagePath: "plugins/c9.ide.layout.classic/preload", themePrefix: options.themePrefix, - defaultTheme: "dark" + defaultTheme: options.defaultTheme || "dark" }, { packagePath: "plugins/c9.ide.tree/tree", @@ -618,7 +618,6 @@ module.exports = function(options) { }, { packagePath: "plugins/c9.cli.bridge/bridge", - port: 17123, startBridge: options.startBridge }, { diff --git a/node_modules/ace/lib/ace/commands/default_commands.js b/node_modules/ace/lib/ace/commands/default_commands.js index f1e267c1..dddce931 100644 --- a/node_modules/ace/lib/ace/commands/default_commands.js +++ b/node_modules/ace/lib/ace/commands/default_commands.js @@ -423,6 +423,12 @@ exports.commands = [{ exec: function() {}, passEvent: true, readOnly: true +}, { + name: "copy", + exec: function(editor) { + // placeholder for replay macro + }, + readOnly: true }, // commands disabled in readOnly mode @@ -439,6 +445,12 @@ exports.commands = [{ }, scrollIntoView: "cursor", multiSelectAction: "forEach" +}, { + name: "paste", + exec: function(editor, text) { + editor.$handlePaste(text); + }, + scrollIntoView: "cursor" }, { name: "removeline", bindKey: bindKey("Ctrl-D", "Command-D"), diff --git a/node_modules/ace/lib/ace/edit_session.js b/node_modules/ace/lib/ace/edit_session.js index bd984221..56c0f0b5 100644 --- a/node_modules/ace/lib/ace/edit_session.js +++ b/node_modules/ace/lib/ace/edit_session.js @@ -132,7 +132,6 @@ var SearchHighlight = require("./search_highlight").SearchHighlight; //} /** - * * Sets up a new `EditSession` and associates it with the given `Document` and `TextMode`. * @param {Document | String} text [If `text` is a `Document`, it associates the `EditSession` with it. Otherwise, a new `Document` is created, with the initial text]{: #textParam} * @param {TextMode} mode [The inital language mode to use for the document]{: #modeParam} @@ -149,9 +148,10 @@ var EditSession = function(text, mode) { this.$undoSelect = true; this.$foldData = []; + this.id = "session" + EditSession.$uid; this.$foldData.toString = function() { return this.join("\n"); - } + }; this.on("changeFold", this.onChangeFold.bind(this)); this.$onChange = this.onChange.bind(this); @@ -167,6 +167,8 @@ var EditSession = function(text, mode) { }; +EditSession.$uid = 0; + (function() { oop.implement(this, EventEmitter); @@ -255,15 +257,17 @@ var EditSession = function(text, mode) { this.$resetRowCache(delta.start.row); var removedFolds = this.$updateInternalDataOnChange(delta); - if (!this.$fromUndo && this.$undoManager && !delta.ignore) { - this.$deltasDoc.push(delta); - if (removedFolds && removedFolds.length != 0) { - this.$deltasFold.push({ + if (!this.$fromUndo && this.$undoManager) { + if (removedFolds && removedFolds.length) { + this.$undoManager.add({ action: "removeFolds", folds: removedFolds - }); + }, this.mergeUndoDeltas); + this.mergeUndoDeltas = true; } - + this.$undoManager.add(delta, this.mergeUndoDeltas); + this.mergeUndoDeltas = true; + this.$informUndoManager.schedule(); } @@ -281,9 +285,6 @@ var EditSession = function(text, mode) { this.selection.moveTo(0, 0); this.$resetRowCache(0); - this.$deltas = []; - this.$deltasDoc = []; - this.$deltasFold = []; this.setUndoManager(this.$undoManager); this.getUndoManager().reset(); }; @@ -364,53 +365,27 @@ var EditSession = function(text, mode) { }; /** - * Sets the undo manager. - * @param {UndoManager} undoManager The new undo manager - * - * - **/ + * Sets the undo manager. + * @param {UndoManager} undoManager The new undo manager + * + * + **/ this.setUndoManager = function(undoManager) { this.$undoManager = undoManager; - this.$deltas = []; - this.$deltasDoc = []; - this.$deltasFold = []; - + if (this.$informUndoManager) this.$informUndoManager.cancel(); - + if (undoManager) { var self = this; - + undoManager.addSession(this); this.$syncInformUndoManager = function() { self.$informUndoManager.cancel(); - - if (self.$deltasFold.length) { - self.$deltas.push({ - group: "fold", - deltas: self.$deltasFold - }); - self.$deltasFold = []; - } - - if (self.$deltasDoc.length) { - self.$deltas.push({ - group: "doc", - deltas: self.$deltasDoc - }); - self.$deltasDoc = []; - } - - if (self.$deltas.length > 0) { - undoManager.execute({ - action: "aceupdate", - args: [self.$deltas, self], - merge: self.mergeUndoDeltas - }); - } self.mergeUndoDeltas = false; - self.$deltas = []; }; this.$informUndoManager = lang.delayedCall(this.$syncInformUndoManager); + } else { + this.$syncInformUndoManager = function() {}; } }; @@ -425,7 +400,11 @@ var EditSession = function(text, mode) { this.$defaultUndoManager = { undo: function() {}, redo: function() {}, - reset: function() {} + reset: function() {}, + add: function() {}, + addSelection: function() {}, + startNewGroup: function() {}, + addSession: function() {}, }; /** @@ -1147,14 +1126,14 @@ var EditSession = function(text, mode) { }; /** - * Removes a range of full lines. This method also triggers the `'change'` event. - * @param {Number} firstRow The first row to be removed - * @param {Number} lastRow The last row to be removed - * @returns {[String]} Returns all the removed lines. - * - * @related Document.removeFullLines - * - **/ + * Removes a range of full lines. This method also triggers the `'change'` event. + * @param {Number} firstRow The first row to be removed + * @param {Number} lastRow The last row to be removed + * @returns {[String]} Returns all the removed lines. + * + * @related Document.removeFullLines + * + **/ this.removeFullLines = function(firstRow, lastRow){ return this.doc.removeFullLines(firstRow, lastRow); }; @@ -1163,34 +1142,30 @@ var EditSession = function(text, mode) { * Reverts previous changes to your document. * @param {Array} deltas An array of previous changes * @param {Boolean} dontSelect [If `true`, doesn't select the range of where the change occured]{: #dontSelect} - * * * @returns {Range} - **/ + **/ this.undoChanges = function(deltas, dontSelect) { if (!deltas.length) return; this.$fromUndo = true; - var lastUndoRange = null; for (var i = deltas.length - 1; i != -1; i--) { var delta = deltas[i]; - if (delta.group == "doc") { - this.doc.revertDeltas(delta.deltas); - lastUndoRange = - this.$getUndoSelection(delta.deltas, true, lastUndoRange); - } else { - delta.deltas.forEach(function(foldDelta) { - this.addFolds(foldDelta.folds); - }, this); + if (delta.action == "insert" || delta.action == "remove") { + this.doc.revertDelta(delta); + } else if (delta.folds) { + this.addFolds(delta.folds); } } + if (!dontSelect) { + // console.log(deltas.selectionBefore + "uuu") + if (deltas.selectionBefore) + this.selection.fromJSON(deltas.selectionBefore); + else + this.selection.setRange(this.$getUndoSelection(deltas, true)); + } this.$fromUndo = false; - lastUndoRange && - this.$undoSelect && - !dontSelect && - this.selection.setSelectionRange(lastUndoRange); - return lastUndoRange; }; /** @@ -1198,41 +1173,39 @@ var EditSession = function(text, mode) { * @param {Array} deltas An array of previous changes * @param {Boolean} dontSelect {:dontSelect} * - * * @returns {Range} - **/ + **/ this.redoChanges = function(deltas, dontSelect) { if (!deltas.length) return; this.$fromUndo = true; - var lastUndoRange = null; for (var i = 0; i < deltas.length; i++) { var delta = deltas[i]; - if (delta.group == "doc") { - this.doc.applyDeltas(delta.deltas); - lastUndoRange = - this.$getUndoSelection(delta.deltas, false, lastUndoRange); + if (delta.action == "insert" || delta.action == "remove") { + this.doc.applyDelta(delta); } } + + if (!dontSelect) { + if (deltas.selectionAfter) + this.selection.fromJSON(deltas.selectionAfter); + else + this.selection.setRange(this.$getUndoSelection(deltas, false)); + } this.$fromUndo = false; - lastUndoRange && - this.$undoSelect && - !dontSelect && - this.selection.setSelectionRange(lastUndoRange); - return lastUndoRange; }; /** * Enables or disables highlighting of the range where an undo occured. * @param {Boolean} enable If `true`, selects the range of the reinserted change - * - **/ + * + **/ this.setUndoSelect = function(enable) { this.$undoSelect = enable; }; - this.$getUndoSelection = function(deltas, isUndo, lastUndoRange) { + this.$getUndoSelection = function(deltas, isUndo) { function isInsert(delta) { return isUndo ? delta.action !== "insert" : delta.action === "insert"; } @@ -1268,60 +1241,36 @@ var EditSession = function(text, mode) { lastDeltaIsInsert = false; } } - - // Check if this range and the last undo range has something in common. - // If true, merge the ranges. - if (lastUndoRange != null) { - if (Range.comparePoints(lastUndoRange.start, range.start) === 0) { - lastUndoRange.start.column += range.end.column - range.start.column; - lastUndoRange.end.column += range.end.column - range.start.column; - } - - var cmp = lastUndoRange.compareRange(range); - if (cmp == 1) { - range.setStart(lastUndoRange.start); - } else if (cmp == -1) { - range.setEnd(lastUndoRange.end); - } - } - return range; }; /** - * Replaces a range in the document with the new `text`. - * - * @param {Range} range A specified Range to replace - * @param {String} text The new text to use as a replacement - * @returns {Object} An object containing the final row and column, like this: - * ``` - * {row: endRow, column: 0} - * ``` - * If the text and range are empty, this function returns an object containing the current `range.start` value. - * If the text is the exact same as what currently exists, this function returns an object containing the current `range.end` value. - * - * - * - * @related Document.replace - * - * - **/ + * Replaces a range in the document with the new `text`. + * + * @param {Range} range A specified Range to replace + * @param {String} text The new text to use as a replacement + * @returns {Object} An object containing the final row and column, like this: + * ``` + * {row: endRow, column: 0} + * ``` + * If the text and range are empty, this function returns an object containing the current `range.start` value. + * If the text is the exact same as what currently exists, this function returns an object containing the current `range.end` value. + * + * @related Document.replace + **/ this.replace = function(range, text) { return this.doc.replace(range, text); }; /** - * Moves a range of text from the given range to the given position. `toPosition` is an object that looks like this: + * Moves a range of text from the given range to the given position. `toPosition` is an object that looks like this: * ```json - * { row: newRowLocation, column: newColumnLocation } + * { row: newRowLocation, column: newColumnLocation } * ``` * @param {Range} fromRange The range of text you want moved within the document * @param {Object} toPosition The location (row and column) where you want to move the text to * @returns {Range} The new range where the text was moved to. - * - * - * - **/ + **/ this.moveText = function(fromRange, toPosition, copy) { var text = this.getTextRange(fromRange); var folds = this.getFoldsInRange(fromRange); @@ -1365,15 +1314,15 @@ var EditSession = function(text, mode) { }; /** - * Indents all the rows, from `startRow` to `endRow` (inclusive), by prefixing each row with the token in `indentString`. - * - * If `indentString` contains the `'\t'` character, it's replaced by whatever is defined by [[EditSession.getTabString `getTabString()`]]. - * @param {Number} startRow Starting row - * @param {Number} endRow Ending row - * @param {String} indentString The indent token - * - * - **/ + * Indents all the rows, from `startRow` to `endRow` (inclusive), by prefixing each row with the token in `indentString`. + * + * If `indentString` contains the `'\t'` character, it's replaced by whatever is defined by [[EditSession.getTabString `getTabString()`]]. + * @param {Number} startRow Starting row + * @param {Number} endRow Ending row + * @param {String} indentString The indent token + * + * + **/ this.indentRows = function(startRow, endRow, indentString) { indentString = indentString.replace(/\t/g, this.getTabString()); for (var row=startRow; row<=endRow; row++) @@ -1381,11 +1330,10 @@ var EditSession = function(text, mode) { }; /** - * Outdents all the rows defined by the `start` and `end` properties of `range`. - * @param {Range} range A range of rows - * - * - **/ + * Outdents all the rows defined by the `start` and `end` properties of `range`. + * @param {Range} range A range of rows + * + **/ this.outdentRows = function (range) { var rowRange = range.collapseRows(); var deleteRange = new Range(0, 0, 0, 0); @@ -1443,34 +1391,34 @@ var EditSession = function(text, mode) { return diff; }; /** - * Shifts all the lines in the document up one, starting from `firstRow` and ending at `lastRow`. - * @param {Number} firstRow The starting row to move up - * @param {Number} lastRow The final row to move up - * @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1. - * - **/ + * Shifts all the lines in the document up one, starting from `firstRow` and ending at `lastRow`. + * @param {Number} firstRow The starting row to move up + * @param {Number} lastRow The final row to move up + * @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1. + * + **/ this.moveLinesUp = function(firstRow, lastRow) { return this.$moveLines(firstRow, lastRow, -1); }; /** - * Shifts all the lines in the document down one, starting from `firstRow` and ending at `lastRow`. - * @param {Number} firstRow The starting row to move down - * @param {Number} lastRow The final row to move down - * @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1. - **/ + * Shifts all the lines in the document down one, starting from `firstRow` and ending at `lastRow`. + * @param {Number} firstRow The starting row to move down + * @param {Number} lastRow The final row to move down + * @returns {Number} If `firstRow` is less-than or equal to 0, this function returns 0. Otherwise, on success, it returns -1. + **/ this.moveLinesDown = function(firstRow, lastRow) { return this.$moveLines(firstRow, lastRow, 1); }; /** - * Duplicates all the text between `firstRow` and `lastRow`. - * @param {Number} firstRow The starting row to duplicate - * @param {Number} lastRow The final row to duplicate - * @returns {Number} Returns the number of new rows added; in other words, `lastRow - firstRow + 1`. - * - * - **/ + * Duplicates all the text between `firstRow` and `lastRow`. + * @param {Number} firstRow The starting row to duplicate + * @param {Number} lastRow The final row to duplicate + * @returns {Number} Returns the number of new rows added; in other words, `lastRow - firstRow + 1`. + * + * + **/ this.duplicateLines = function(firstRow, lastRow) { return this.$moveLines(firstRow, lastRow, 0); }; @@ -1545,8 +1493,7 @@ var EditSession = function(text, mode) { * Sets whether or not line wrapping is enabled. If `useWrapMode` is different than the current value, the `'changeWrapMode'` event is emitted. * @param {Boolean} useWrapMode Enable (or disable) wrap mode * - * - **/ + **/ this.setUseWrapMode = function(useWrapMode) { if (useWrapMode != this.$useWrapMode) { this.$useWrapMode = useWrapMode; @@ -1565,9 +1512,9 @@ var EditSession = function(text, mode) { }; /** - * Returns `true` if wrap mode is being used; `false` otherwise. - * @returns {Boolean} - **/ + * Returns `true` if wrap mode is being used; `false` otherwise. + * @returns {Boolean} + **/ this.getUseWrapMode = function() { return this.$useWrapMode; }; @@ -1581,8 +1528,7 @@ var EditSession = function(text, mode) { * @param {Number} min The minimum wrap value (the left side wrap) * @param {Number} max The maximum wrap value (the right side wrap) * - * - **/ + **/ this.setWrapLimitRange = function(min, max) { if (this.$wrapLimitRange.min !== min || this.$wrapLimitRange.max !== max) { this.$wrapLimitRange = { min: min, max: max }; @@ -1594,12 +1540,12 @@ var EditSession = function(text, mode) { }; /** - * This should generally only be called by the renderer when a resize is detected. - * @param {Number} desiredLimit The new wrap limit - * @returns {Boolean} - * - * @private - **/ + * This should generally only be called by the renderer when a resize is detected. + * @param {Number} desiredLimit The new wrap limit + * @returns {Boolean} + * + * @private + **/ this.adjustWrapLimit = function(desiredLimit, $printMargin) { var limits = this.$wrapLimitRange; if (limits.max < 0) diff --git a/node_modules/ace/lib/ace/editor.js b/node_modules/ace/lib/ace/editor.js index 500cfc45..5cc23e95 100644 --- a/node_modules/ace/lib/ace/editor.js +++ b/node_modules/ace/lib/ace/editor.js @@ -111,21 +111,25 @@ var Editor = function(renderer, session) { oop.implement(this, EventEmitter); this.$initOperationListeners = function() { - function last(a) {return a[a.length - 1]} - - this.selections = []; this.commands.on("exec", this.startOperation.bind(this), true); this.commands.on("afterExec", this.endOperation.bind(this), true); this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this)); - + + // todo: add before change events? this.on("change", function() { - this.curOp || this.startOperation(); + if (!this.curOp) { + this.startOperation(); + this.curOp.selectionBefore = this.$lastSel; + } this.curOp.docChanged = true; }.bind(this), true); - + this.on("changeSelection", function() { - this.curOp || this.startOperation(); + if (!this.curOp) { + this.startOperation(); + this.curOp.selectionBefore = this.$lastSel; + } this.curOp.selectionChanged = true; }.bind(this), true); }; @@ -144,19 +148,18 @@ var Editor = function(renderer, session) { } this.$opResetTimer.schedule(); - this.curOp = { + this.curOp = this.session.curOp = { command: commadEvent.command || {}, args: commadEvent.args, scrollTop: this.renderer.scrollTop }; - - // this.selections.push(this.selection.toJSON()); + this.curOp.selectionBefore = this.selection.toJSON(); }; this.endOperation = function(e) { if (this.curOp) { if (e && e.returnValue === false) - return this.curOp = null; + return (this.curOp = null); this._signal("beforeEndOperation"); var command = this.curOp.command; var scrollIntoView = command && command.scrollIntoView; @@ -185,7 +188,12 @@ var Editor = function(renderer, session) { if (scrollIntoView == "animate") this.renderer.animateScrolling(this.curOp.scrollTop); } + var sel = this.selection.toJSON(); + this.curOp.selectionAfter = sel; + this.$lastSel = this.selection.toJSON(); + // console.log(this.$lastSel+" endOP") + this.session.getUndoManager().addSelection(sel); this.prevOp = this.curOp; this.curOp = null; } @@ -903,10 +911,10 @@ var Editor = function(renderer, session) { * **/ this.onPaste = function(text) { - // todo this should change when paste becomes a command - if (this.$readOnly) - return; - + this.commands.exec("paste", this, text); + }; + + this.$handlePaste = function(text) { var e = {text: text}; this._signal("paste", e); text = e.text; @@ -927,7 +935,6 @@ var Editor = function(renderer, session) { this.session.insert(range.start, lines[i]); } } - this.renderer.scrollCursorIntoView(); }; this.execCommand = function(command, args) { @@ -2487,7 +2494,7 @@ var Editor = function(renderer, session) { * @related UndoManager.undo **/ this.undo = function() { - this.session.getUndoManager().undo(); + this.session.getUndoManager().undo(this.session); this.renderer.scrollCursorIntoView(null, 0.5); }; @@ -2496,7 +2503,7 @@ var Editor = function(renderer, session) { * @related UndoManager.redo **/ this.redo = function() { - this.session.getUndoManager().redo(); + this.session.getUndoManager().redo(this.session); this.renderer.scrollCursorIntoView(null, 0.5); }; diff --git a/node_modules/ace/lib/ace/split.js b/node_modules/ace/lib/ace/split.js index 878b0dc3..e960c128 100644 --- a/node_modules/ace/lib/ace/split.js +++ b/node_modules/ace/lib/ace/split.js @@ -42,8 +42,6 @@ var EditSession = require("./edit_session").EditSession; /** * @class Split * - * - * **/ @@ -217,14 +215,7 @@ var Split = function(container, theme, splits) { var s = new EditSession(session.getDocument(), session.getMode()); var undoManager = session.getUndoManager(); - if (undoManager) { - var undoManagerProxy = new UndoManagerProxy(undoManager, s); - s.setUndoManager(undoManagerProxy); - } - - // Overwrite the default $informUndoManager function such that new delas - // aren't added to the undo manager from the new and the old session. - s.$informUndoManager = lang.delayedCall(function() { s.$deltas = []; }); + s.setUndoManager(undoManager); // Copy over 'settings' from the session. s.setTabSize(session.getTabSize()); @@ -331,43 +322,5 @@ var Split = function(container, theme, splits) { }).call(Split.prototype); - -function UndoManagerProxy(undoManager, session) { - this.$u = undoManager; - this.$doc = session; -} - -(function() { - this.execute = function(options) { - this.$u.execute(options); - }; - - this.undo = function() { - var selectionRange = this.$u.undo(true); - if (selectionRange) { - this.$doc.selection.setSelectionRange(selectionRange); - } - }; - - this.redo = function() { - var selectionRange = this.$u.redo(true); - if (selectionRange) { - this.$doc.selection.setSelectionRange(selectionRange); - } - }; - - this.reset = function() { - this.$u.reset(); - }; - - this.hasUndo = function() { - return this.$u.hasUndo(); - }; - - this.hasRedo = function() { - return this.$u.hasRedo(); - }; -}).call(UndoManagerProxy.prototype); - exports.Split = Split; }); diff --git a/node_modules/ace/lib/ace/test/all_browser.js b/node_modules/ace/lib/ace/test/all_browser.js index 7ac5092e..71e96e2f 100644 --- a/node_modules/ace/lib/ace/test/all_browser.js +++ b/node_modules/ace/lib/ace/test/all_browser.js @@ -5,9 +5,9 @@ require("ace/lib/fixoldbrowsers"); var AsyncTest = require("asyncjs").test; var async = require("asyncjs"); -var passed = 0 -var failed = 0 -var log = document.getElementById("log") +var passed = 0; +var failed = 0; +var log = document.getElementById("log"); var testNames = [ "ace/anchor_test", @@ -55,6 +55,7 @@ var testNames = [ "ace/snippets_test", "ace/token_iterator_test", "ace/tokenizer_test", + "ace/undomanager_test", "ace/virtual_renderer_test" ]; @@ -64,13 +65,30 @@ for (var i in testNames) { html.push("", href.replace(/^ace\//, "") ,"
"); } + +if (location.search.indexOf("show=1") != -1) { + var VirtualRenderer = require("ace/virtual_renderer").VirtualRenderer; + require("ace/test/mockrenderer").MockRenderer = function() { + var el = document.createElement("div"); + el.style.position = "fixed"; + el.style.left = "20px"; + el.style.top = "30px"; + el.style.width = "500px"; + el.style.height = "300px"; + document.body.appendChild(el); + + return new VirtualRenderer(el); + }; +} + + var nav = document.createElement("div"); nav.innerHTML = html.join(""); nav.style.cssText = "position:absolute;right:0;top:0"; document.body.appendChild(nav); if (location.search) - testNames = location.search.substr(1).split(",") + testNames = location.search.substr(1).split(","); var filter = location.hash.substr(1); @@ -89,7 +107,7 @@ require(testNames, function() { test[method] = undefined; }); } - return AsyncTest.testcase(test) + return AsyncTest.testcase(test); }, AsyncTest.TestGenerator) .run() .each(function(test, next) { @@ -102,16 +120,16 @@ require(testNames, function() { var node = document.createElement("div"); node.className = test.passed ? "passed" : "failed"; - var name = test.name + var name = test.name; if (test.suiteName) - name = test.suiteName + ": " + test.name + name = test.suiteName + ": " + test.name; - var msg = "[" + test.count + "/" + test.index + "] " + name + " " + (test.passed ? "OK" : "FAIL") + var msg = "[" + test.count + "/" + test.index + "] " + name + " " + (test.passed ? "OK" : "FAIL"); if (!test.passed) { if (test.err.stack) - var err = test.err.stack + var err = test.err.stack; else - var err = test.err + var err = test.err; console.error(msg); console.error(err); @@ -123,13 +141,13 @@ require(testNames, function() { node.innerHTML = msg; log.appendChild(node); - next() + next(); }) .each(function(test) { if (test.passed) - passed += 1 + passed += 1; else - failed += 1 + failed += 1; }) .end(function() { log.innerHTML += [ @@ -140,11 +158,11 @@ require(testNames, function() { "Total number of tests: " + (passed + failed) + "
", (passed ? "Passed tests: " + passed + "
" : ""), (failed ? "Failed tests: " + failed + "
" : "") - ].join("") + ].join(""); console.log("Total number of tests: " + (passed + failed)); console.log("Passed tests: " + passed); console.log("Failed tests: " + failed); - }) + }); }); }); diff --git a/node_modules/ace/lib/ace/undomanager.js b/node_modules/ace/lib/ace/undomanager.js index 6da50a8b..1c76f4f4 100644 --- a/node_modules/ace/lib/ace/undomanager.js +++ b/node_modules/ace/lib/ace/undomanager.js @@ -32,25 +32,26 @@ define(function(require, exports, module) { "use strict"; /** - * - * * This object maintains the undo stack for an [[EditSession `EditSession`]]. * @class UndoManager **/ /** - * - * * Resets the current undo state and creates a new `UndoManager`. * * @constructor **/ var UndoManager = function() { + this.$maxRev = 0; + this.$fromUndo = false; this.reset(); }; (function() { - + + this.addSession = function(session) { + this.$session = session; + }; /** * Provides a means for implementing your own undo manager. `options` has one property, `args`, an [[Array `Array`]], with two elements: * @@ -60,156 +61,523 @@ var UndoManager = function() { * @param {Object} options Contains additional properties * **/ - this.execute = function(options) { - // Normalize deltas for storage. - // var deltaSets = this.$serializeDeltas(options.args[0]); - var deltaSets = options.args[0]; - // Add deltas to undo stack. - this.$doc = options.args[1]; - if (options.merge && this.hasUndo()){ - this.dirtyCounter--; - deltaSets = this.$undoStack.pop().concat(deltaSets); + this.add = function(delta, allowMerge, session) { + if (this.$fromUndo) return; + if (delta == this.$lastDelta) return; + if (allowMerge === false || !this.lastDeltas) { + this.lastDeltas = []; + this.$undoStack.push(this.lastDeltas); + delta.id = this.$rev = ++this.$maxRev; } - this.$undoStack.push(deltaSets); - - // Reset redo stack. - this.$redoStack = []; - if (this.dirtyCounter < 0) { - // The user has made a change after undoing past the last clean state. - // We can never get back to a clean state now until markClean() is called. - this.dirtyCounter = NaN; - } - this.dirtyCounter++; + if (delta.action == "remove" || delta.action == "insert") + this.$lastDelta = delta; + this.lastDeltas.push(delta); }; - + + this.addSelection = function(selection, rev) { + this.selections.push({ + value: selection, + rev: rev || this.$rev + }); + }; + + this.startNewGroup = function() { + this.lastDeltas = null; + return this.$rev; + }; + + this.markIgnored = function(from, to) { + if (to == null) to = this.$rev + 1; + var stack = this.$undoStack; + for (var i = stack.length; i--;) { + var delta = stack[i][0]; + if (delta.id <= from) + break; + if (delta.id < to) + delta.ignore = true; + } + this.lastDeltas = null; + }; + + this.getSelection = function(rev, after) { + var stack = this.selections; + for (var i = stack.length; i--;) { + var selection = stack[i]; + if (selection.rev < rev) { + if (after) + selection = stack[i + 1]; + return selection; + } + } + }; + + this.getRevision = function() { + return this.$rev; + }; + + this.getDeltas = function(from, to) { + if (to == null) to = this.$rev + 1; + var stack = this.$undoStack; + var end = null, start = 0; + for (var i = stack.length; i--;) { + var delta = stack[i][0]; + if (delta.id < to && !end) + end = i+1; + if (delta.id <= from) { + start = i + 1; + break; + } + } + return stack.slice(start, end); + }; + + this.getChangedRanges = function(from, to) { + if (to == null) to = this.$rev + 1; + + }; + + this.getChangedLines = function(from, to) { + if (to == null) to = this.$rev + 1; + + }; + /** * [Perform an undo operation on the document, reverting the last change.]{: #UndoManager.undo} * @param {Boolean} dontSelect {:dontSelect} * - * * @returns {Range} The range of the undo. **/ - this.undo = function(dontSelect) { - var deltaSets = this.$undoStack.pop(); + this.undo = function(session, dontSelect) { + this.lastDeltas = null; + var stack = this.$undoStack; + + if (!rearrangeUndoStack(stack, stack.length)) + return; + + if (this.$redoStackBaseRev !== this.$rev && this.$redoStack.length) + this.$redoStack = []; + + this.$fromUndo = true; + + var deltaSet = stack.pop(); var undoSelectionRange = null; - if (deltaSets) { - undoSelectionRange = this.$doc.undoChanges(this.$deserializeDeltas(deltaSets), dontSelect); - this.$redoStack.push(deltaSets); - this.dirtyCounter--; + if (deltaSet && deltaSet.length) { + undoSelectionRange = session.undoChanges(deltaSet, dontSelect); + this.$redoStack.push(deltaSet); + this.$syncRev(); } + + this.$fromUndo = false; return undoSelectionRange; }; - + /** * [Perform a redo operation on the document, reimplementing the last change.]{: #UndoManager.redo} * @param {Boolean} dontSelect {:dontSelect} * - * **/ - this.redo = function(dontSelect) { - var deltaSets = this.$redoStack.pop(); - var redoSelectionRange = null; - if (deltaSets) { - redoSelectionRange = - this.$doc.redoChanges(this.$deserializeDeltas(deltaSets), dontSelect); - this.$undoStack.push(deltaSets); - this.dirtyCounter++; + this.redo = function(session, dontSelect) { + this.lastDeltas = null; + + this.$fromUndo = true; + if (this.$redoStackBaseRev != this.$rev) { + var diff = this.getDeltas(this.$redoStackBaseRev, this.$rev + 1); + rebaseRedoStack(this.$redoStack, diff); + this.$redoStackBaseRev = this.$rev; + this.$redoStack.forEach(function(x) { + x[0].id = ++this.$maxRev; + }, this); } + var deltaSet = this.$redoStack.pop(); + var redoSelectionRange = null; + + if (deltaSet) { + redoSelectionRange = session.redoChanges(deltaSet, dontSelect); + this.$undoStack.push(deltaSet); + this.$syncRev(); + } + this.$fromUndo = false; + return redoSelectionRange; }; + + this.$syncRev = function() { + var stack = this.$undoStack; + var nextDelta = stack[stack.length - 1]; + var id = nextDelta && nextDelta[0].id || 0; + this.$redoStackBaseRev = id; + this.$rev = id; + }; /** - * * Destroys the stack of undo and redo redo operations. **/ this.reset = function() { + this.lastDeltas = null; + this.$lastDelta = null; this.$undoStack = []; this.$redoStack = []; - this.dirtyCounter = 0; + this.$rev = 0; + this.mark = 0; + this.$redoStackBaseRev = this.$rev; + this.selections = []; }; + /** - * * Returns `true` if there are undo operations left to perform. * @returns {Boolean} **/ - this.hasUndo = function() { + this.canUndo = function() { return this.$undoStack.length > 0; }; /** - * * Returns `true` if there are redo operations left to perform. * @returns {Boolean} **/ - this.hasRedo = function() { + this.canRedo = function() { return this.$redoStack.length > 0; }; - + /** - * * Marks the current status clean **/ - this.markClean = function() { - this.dirtyCounter = 0; + this.bookmark = function(rev) { + if (rev == undefined) + rev = this.$rev; + this.mark = rev; }; /** - * * Returns if the current status is clean * @returns {Boolean} **/ - this.isClean = function() { - return this.dirtyCounter === 0; + this.isAtBookmark = function() { + return this.$rev === this.mark; }; - // Serializes deltaSets to reduce memory usage. - this.$serializeDeltas = function(deltaSets) { - return cloneDeltaSetsObj(deltaSets, $serializeDelta); - }; - - // Deserializes deltaSets to allow application to the document. - this.$deserializeDeltas = function(deltaSets) { - return cloneDeltaSetsObj(deltaSets, $deserializeDelta); - }; - - function $serializeDelta(delta){ - return { - action: delta.action, - start: delta.start, - end: delta.end, - lines: delta.lines.length == 1 ? null : delta.lines, - text: delta.lines.length == 1 ? delta.lines[0] : null, - }; - } + this.toJSON = function() { - function $deserializeDelta(delta) { - return { - action: delta.action, - start: delta.start, - end: delta.end, - lines: delta.lines || [delta.text] - }; - } + }; - function cloneDeltaSetsObj(deltaSets_old, fnGetModifiedDelta) { - var deltaSets_new = new Array(deltaSets_old.length); - for (var i = 0; i < deltaSets_old.length; i++) { - var deltaSet_old = deltaSets_old[i]; - var deltaSet_new = { group: deltaSet_old.group, deltas: new Array(deltaSet_old.length)}; - - for (var j = 0; j < deltaSet_old.deltas.length; j++) { - var delta_old = deltaSet_old.deltas[j]; - deltaSet_new.deltas[j] = fnGetModifiedDelta(delta_old); - } - - deltaSets_new[i] = deltaSet_new; - } - return deltaSets_new; - } + this.fromJSON = function() { + + }; + + this.hasUndo = this.canUndo; + this.hasRedo = this.canRedo; + this.isClean = this.isAtBookmark; + this.markClean = this.bookmark; }).call(UndoManager.prototype); +function rearrangeUndoStack(stack, pos) { + for (var i = pos; i--; ) { + var deltaSet = stack[i]; + if (deltaSet && !deltaSet[0].ignore) { + while(i < pos - 1) { + var swapped = swapGroups(stack[i], stack[i + 1]); + stack[i] = swapped[0]; + stack[i + 1] = swapped[1]; + i++; + } + return true; + } + } +} + +var Range = require("./range").Range; +var cmp = Range.comparePoints; +var comparePoints = Range.comparePoints; + +function $updateMarkers(delta) { + var isInsert = delta.action == "insert"; + var start = delta.start; + var end = delta.end; + var rowShift = (end.row - start.row) * (isInsert ? 1 : -1); + var colShift = (end.column - start.column) * (isInsert ? 1 : -1); + if (isInsert) end = start; + + for (var i in this.marks) { + var point = this.marks[i]; + var cmp = comparePoints(point, start); + if (cmp < 0) { + continue; // delta starts after the range + } + if (cmp === 0) { + if (isInsert) { + if (point.bias == 1) { + cmp = 1; + } + else { + point.bias == -1; + continue; + } + } + } + var cmp2 = isInsert ? cmp : comparePoints(point, end); + if (cmp2 > 0) { + point.row += rowShift; + point.column += point.row == end.row ? colShift : 0; + continue; + } + if (!isInsert && cmp2 <= 0) { + point.row = start.row; + point.column = start.column; + if (cmp2 === 0) + point.bias = 1; + } + } +} + + + +function clonePos(pos) { + return {row: pos.row,column: pos.column}; +} +function cloneDelta(d) { + return { + start: clonePos(d.start), + end: clonePos(d.end), + action: d.action, + lines: d.lines.slice() + }; +} +function stringifyDelta(d) { + d = d || this; + if (Array.isArray(d)) { + return d.map(stringifyDelta).join("\n"); + } + var type = ""; + if (d.action) { + type = d.action == "insert" ? "+" : "-"; + type += "[" + d.lines + "]"; + } else if (d.value) { + if (Array.isArray(d.value)) { + type = d.value.map(stringifyRange).join("\n"); + } else { + type = stringifyRange(d.value); + } + } + if (d.start) { + type += stringifyRange(d); + } + if (d.id || d.rev) { + type += "\t(" + (d.id || d.rev) + ")"; + } + return type; +} +function stringifyRange(r) { + return r.start.row + ":" + r.start.column + + "=>" + r.end.row + ":" + r.end.column; +} +/* + * i i d1 d2 + * |/ |/ d2.s >= d1.e shift(d2, d1, -1) + * d2.s <= d1.s shift(d1, d2, +1) + * d1.s < d2.s < d1.e // can split + * + * i r d1 d2 + * |/ |\ d2.s >= d1.e shift(d2, d1, -1) + * d2.e <= d1.s shift(d1, d2, -1) + * else // can't swap + * + * r i d1 d2 + * |\ |/ d2.s >= d1.s shift(d2, d1, +1) + * d2.s <= d1.s shift(d1, d2, +1) + * // no else + * + * r r d1 d2 + * |\ |\ d2.s >= d1.s shift(d2, d1, +1) + * d2.e <= d1.s shift(d1, d2, -1) + * d2.s < d1.s < d2.e // can split + */ + +function swap(d1, d2) { + var i1 = d1.action == "insert"; + var i2 = d2.action == "insert"; + + if (i1 && i2) { + if (cmp(d2.start, d1.end) >= 0) { + shift(d2, d1, -1); + } else if (cmp(d2.start, d1.start) <= 0) { + shift(d1, d2, +1); + } else { + return null; + } + } else if (i1 && !i2) { + if (cmp(d2.start, d1.end) >= 0) { + shift(d2, d1, -1); + } else if (cmp(d2.end, d1.start) <= 0) { + shift(d1, d2, -1); + } else { + return null; + } + } else if (!i1 && i2) { + if (cmp(d2.start, d1.start) >= 0) { + shift(d2, d1, +1); + } else if (cmp(d2.start, d1.start) <= 0) { + shift(d1, d2, +1); + } else { + return null; + } + } else if (!i1 && !i2) { + if (cmp(d2.start, d1.start) >= 0) { + shift(d2, d1, +1); + } else if (cmp(d2.end, d1.start) <= 0) { + shift(d1, d2, -1); + } else { + return null; + } + } + return [d2, d1]; +} +function swapGroups(ds1, ds2) { + for (var i = ds1.length; i--; ) { + for (var j = 0; j < ds2.length; j++) { + if (!swap(ds1[i], ds2[j])) { + // rollback, we have to undo ds2 first + while (i < ds1.length) { + while (j--) { + swap(ds2[j], ds1[i]); + } + j = ds2.length; + i++; + } + return [ds1, ds2]; + } + } + } + ds1.selectionBefore = ds2.selectionBefore = + ds1.selectionAfter = ds2.selectionAfter = null; + return [ds2, ds1]; +} + +/* + d2 xform(d1, c1) = [d2, c2] + o<---o xform(c1, d1) = [c2, d2] + c2 | | d1 + o<---o + c1 +*/ +function xform(d1, c1) { + var i1 = d1.action == "insert"; + var i2 = c1.action == "insert"; + + if (i1 && i2) { + if (cmp(d1.start, c1.start) < 0) { + shift(c1, d1, 1); + } else { + shift(d1, c1, 1); + } + } else if (i1 && !i2) { + if (cmp(d1.start, c1.end) >= 0) { + shift(d1, c1, -1); + } else if (cmp(d1.start, c1.start) <= 0) { + shift(c1, d1, +1); + } else { + shift(d1, Range.fromPoints(c1.start, d1.start), -1); + shift(c1, d1, +1); + } + } else if (!i1 && i2) { + if (cmp(c1.start, d1.end) >= 0) { + shift(c1, d1, -1); + } else if (cmp(c1.start, d1.start) <= 0) { + shift(d1, c1, +1); + } else { + shift(c1, Range.fromPoints(d1.start, c1.start), -1); + shift(d1, c1, +1); + } + } else if (!i1 && !i2) { + if (cmp(c1.start, d1.end) >= 0) { + shift(c1, d1, -1); + } else if (cmp(c1.end, d1.start) <= 0) { + shift(d1, c1, -1); + } else { + var before, after; + if (cmp(d1.start, c1.start) < 0) { + before = d1; + d1 = splitDelta(d1, c1.start); + } + if (cmp(d1.end, c1.end) > 0) { + after = splitDelta(d1, c1.end); + } + + shiftPos(c1.end, d1.start, d1.end, -1); + if (after && !before) { + d1.lines = after.lines; + d1.start = after.start; + d1.end = after.end; + after = d1; + } + + return [c1, before, after].filter(Boolean); + } + } + return [c1, d1]; +} + +function shift(d1, d2, dir) { + shiftPos(d1.start, d2.start, d2.end, dir); + shiftPos(d1.end, d2.start, d2.end, dir); +} +function shiftPos(pos, start, end, dir) { + if (pos.row == (dir == 1 ? start : end).row) { + pos.column += dir * (end.column - start.column); + } + pos.row += dir * (end.row - start.row); +} +function splitDelta(c, pos) { + var lines = c.lines; + var end = c.end; + c.end = clonePos(pos); + var rowsBefore = c.end.row - c.start.row; + var otherLines = lines.splice(rowsBefore, lines.length); + + var col = rowsBefore ? pos.column : pos.column - c.start.column; + lines.push(otherLines[0].substring(0, col)); + otherLines[0] = otherLines[0].substr(col) ; + var rest = { + start: clonePos(pos), + end: end, + lines: otherLines, + action: c.action + }; + return rest; +} + +function moveDeltasByOne(redoStack, d) { + d = cloneDelta(d); + for (var j = redoStack.length; j--;) { + var deltaSet = redoStack[j]; + for (var i = deltaSet.length; i--> 0;) { + var x = deltaSet[i]; + var xformed = xform(x, d); + d = xformed[0]; + if (xformed.length != 2) { + if (xformed[2]) { + redoStack.splice(i + 1, 1, xformed[1], xformed[2]); + i++; + } else if (!xformed[1]) { + redoStack.splice(i, 1); + i--; + } + } + } + } + return redoStack; +} +function rebaseRedoStack(redoStack, deltaSets) { + for (var i = 0; i < deltaSets.length; i++) { + var deltas = deltaSets[i]; + for (var j = 0; j < deltas.length; j++) { + moveDeltasByOne(redoStack, deltas[j]); + } + } +} + exports.UndoManager = UndoManager; + }); diff --git a/node_modules/ace/lib/ace/undomanager_test.js b/node_modules/ace/lib/ace/undomanager_test.js new file mode 100644 index 00000000..4a9c1028 --- /dev/null +++ b/node_modules/ace/lib/ace/undomanager_test.js @@ -0,0 +1,111 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +if (typeof process !== "undefined") { + require("amd-loader"); + require("./test/mockdom"); +} + +define(function(require, exports, module) { +"use strict"; + +require("./multi_select"); +var assert = require("./test/assertions"); +var Range = require("./range").Range; +var Editor = require("./editor").Editor; +var EditSession = require("./edit_session").EditSession; +var MockRenderer = require("./test/mockrenderer").MockRenderer; +var UndoManager = require("./undomanager").UndoManager; + +var editor; + + +module.exports = { + + name: "ACE undoManager.js", + + "test: reabsing": function() { + var session = new EditSession(""); + var editor = new Editor(new MockRenderer(), session); + var undoManager = new UndoManager(); + session.setUndoManager(undoManager); + + session.setValue("012345-012345-012345"); + session.insert({row: 0, column: 0}, "xx"); + session.markUndoGroup(); + session.remove(new Range(0, 10, 0, 15)); + session.markUndoGroup(); + session.insert({row: 0, column: 5}, "yy"); + session.markUndoGroup(); + editor.undo(); + editor.undo(); + var rev = session.getUndoManager().startNewGroup(); + session.insert({row: 0, column: 5}, "z\nz"); + session.getUndoManager().markIgnored(rev); + // editor.undo() + editor.redo(); + editor.redo(); + var val1 = editor.getValue(); + editor.undo(); + editor.undo(); + editor.undo(); + + editor.redo(); + editor.redo(); + editor.redo(); + var val2 = editor.getValue(); + assert.equal(val1, val2); + }, + "test: conflicting deletes": function() { + var session = new EditSession(""); + var editor = new Editor(new MockRenderer(), session); + var undoManager = new UndoManager(); + session.setUndoManager(undoManager); + + session.setValue("012345\nabcdefg\nxyz"); + session.remove(new Range(0, 2, 0, 4)); + assert.equal(session.getLine(0), "0145"); + session.markUndoGroup(); + editor.undo(); + session.remove(new Range(0, 1, 0, 5)); + assert.equal(session.getLine(0), "05"); + session.markUndoGroup(); + editor.redo(); + assert.equal(session.getLine(0), "05"); + editor.undo(); + assert.equal(session.getLine(0), "012345"); + } +}; + +}); + +if (typeof module !== "undefined" && module === require.main) { + require("asyncjs").test.testcase(module.exports).exec(); +} diff --git a/node_modules/vfs-child/parent.js b/node_modules/vfs-child/parent.js index 38474005..733570c6 100644 --- a/node_modules/vfs-child/parent.js +++ b/node_modules/vfs-child/parent.js @@ -15,7 +15,7 @@ function Parent(fsOptions) { options.uid = fsOptions.uid; delete fsOptions.uid; } - options.customFds = [-1, -1, 2]; + options.stdio = options.customFds = [-1, -1, 2]; var args = [require.resolve('./child.js'), JSON.stringify(fsOptions)]; var executablePath = process.execPath; var child; diff --git a/node_modules/vfs-local/localfs.js b/node_modules/vfs-local/localfs.js index d43c1756..6e106dad 100644 --- a/node_modules/vfs-local/localfs.js +++ b/node_modules/vfs-local/localfs.js @@ -1828,8 +1828,13 @@ module.exports = function setup(fsOptions) { if (options.idle) options.command = "echo '[Idle]'"; - if (options.terminal) - args.push("export ISOUTPUTPANE=0;" + BASH + " -l"); + if (options.terminal) { + args.push("export ISOUTPUTPANE=0;" + + (options.defaultEditor + ? " export EDITOR='`which c9` open --wait'; " + : "") + + BASH + " -l"); + } else if (options.command) args.push((BASH + " -l -c '" @@ -2334,6 +2339,9 @@ module.exports = function setup(fsOptions) { err.code = "EEXIST"; return callback(err, { api: apis[name] }); } + + if (options.redefine && apis[name] && apis[name].destroy) + apis[name].destroy(); var fn; @@ -2394,6 +2402,9 @@ module.exports = function setup(fsOptions) { } function unextend(name, options, callback) { + if (apis[name] && apis[name].destroy) + apis[name].destroy(); + delete apis[name]; callback(null, {}); } diff --git a/node_modules/vfs-socket/worker.js b/node_modules/vfs-socket/worker.js index 1d8547d6..9512eb96 100644 --- a/node_modules/vfs-socket/worker.js +++ b/node_modules/vfs-socket/worker.js @@ -162,6 +162,9 @@ function Worker(vfs) { var nextStreamID = 1; function storeStream(stream) { + if (stream.token) + return stream.token; + nextStreamID = (nextStreamID + 1) % 10000; while (streams.hasOwnProperty(nextStreamID)) { nextStreamID = (nextStreamID + 1) % 10000; } var id = nextStreamID; @@ -188,6 +191,7 @@ function Worker(vfs) { remote.onClose(id); }); var token = {id: id}; + stream.token = token; if (stream.hasOwnProperty("readable")) token.readable = stream.readable; if (stream.hasOwnProperty("writable")) token.writable = stream.writable; return token; @@ -195,6 +199,9 @@ function Worker(vfs) { function storeProcess(process, onlyPid) { var pid = process.pid; + if (processes.token) + return onlyPid ? process.pid : process.token; + processes[pid] = process; process.on("exit", function (code, signal) { delete processes[pid]; @@ -215,11 +222,13 @@ function Worker(vfs) { code: code }, callback || function() {}); }; + + var token = {pid: pid}; + process.token = token; if (onlyPid) return pid; - var token = {pid: pid}; token.stdin = storeStream(process.stdin); token.stdout = storeStream(process.stdout); token.stderr = storeStream(process.stderr); @@ -230,8 +239,8 @@ function Worker(vfs) { if (!pty || processes[pty.pid] == pty) // Pty is returned twice return pty && pty.token; - var pid = storeProcess(pty, true); - var token = storeStream(pty); + var pid = storeProcess(pty, true); delete pty.token; + var token = storeStream(pty); delete pty.token; token.pid = pid; pty.token = token; diff --git a/package.json b/package.json index 2dc5c639..0a942d39 100644 --- a/package.json +++ b/package.json @@ -60,12 +60,12 @@ "c9.ide.language.javascript.tern": "#7aab8b0b6a", "c9.ide.language.javascript.infer": "#cfec494a3c", "c9.ide.language.jsonalyzer": "#21b64e5820", - "c9.ide.collab": "#edef363853", + "c9.ide.collab": "#da4d09ae6a", "c9.ide.local": "#2bfd7ff051", "c9.ide.find": "#6cc6d3379d", "c9.ide.find.infiles": "#72582de3cd", "c9.ide.find.replace": "#e4daf722b8", - "c9.ide.run.debug": "#638e6b00b3", + "c9.ide.run.debug": "#23a188b91a", "c9.automate": "#47e2c429c9", "c9.ide.ace.emmet": "#e5f1a92ac3", "c9.ide.ace.gotoline": "#4d1a93172c", @@ -81,9 +81,9 @@ "c9.ide.fontawesome": "#781602c5d8", "c9.ide.format": "#f51451ac57", "c9.ide.help.support": "#60e88f5680", - "c9.ide.imgeditor": "#08bbc53578", + "c9.ide.imgeditor": "#ed89162aa7", "c9.ide.immediate": "#6845a93705", - "c9.ide.installer": "#a1e01c07a3", + "c9.ide.installer": "#9bfeb1f703", "c9.ide.mount": "#32e79866ee", "c9.ide.navigate": "#64156c7f4a", "c9.ide.newresource": "#f1f0624768", @@ -99,7 +99,7 @@ "c9.ide.run": "#71c5562e42", "c9.ide.run.build": "#ad45874c88", "c9.ide.run.debug.xdebug": "#b91d23f48b", - "c9.ide.save": "#b876d87d55", + "c9.ide.save": "#3cb206c168", "c9.ide.terminal.monitor": "#b0b4d03280", "c9.ide.theme.flat": "#b1d65fa9bb", "c9.ide.threewaymerge": "#229382aa0b", diff --git a/plugins/c9.cli.bridge/bridge-client.js b/plugins/c9.cli.bridge/bridge-client.js index a283cf0c..f27353be 100644 --- a/plugins/c9.cli.bridge/bridge-client.js +++ b/plugins/c9.cli.bridge/bridge-client.js @@ -5,7 +5,7 @@ */ define(function(require, exports, module) { main.consumes = ["c9", "Plugin", "net"]; - main.provides = ["bridge-client"]; + main.provides = ["bridge.client"]; return main; function main(options, imports, register) { @@ -13,27 +13,61 @@ define(function(require, exports, module) { var c9 = imports.c9; var net = imports.net; + var JSONStream = require("./json-stream"); + /***** Initialization *****/ var plugin = new Plugin("Ajax.org", main.consumes); // var emit = plugin.getEmitter(); - var PORT = options.port || 17123; + var counter = 0; + var SOCKET = c9.platform == "win32" + ? "\\\\.\\pipe\\.c9\\bridge.socket" + : c9.home + "/.c9/bridge.socket"; /***** Methods *****/ function send(message, callback) { - net.connect(PORT, {}, function(err, stream) { + net.connect(SOCKET, {}, function(err, stream) { if (err) return callback(err); - stream.write(JSON.stringify(message)); - stream.end(); + var jstream = new JSONStream(stream); + var msgId = generateMessageId(); + var done; - callback(); + jstream.write({ + id: msgId, + message: message + }); + + jstream.on("data", function(payload){ + if (payload.id == msgId && !done) { + done = true; + callback(null, payload.message); + stream.end(); + } + }); + + jstream.on("error", function(err){ + if (done) return; + callback(err); + done = true; + }); + + jstream.on("close", function(){ + if (done) return; + callback(new Error("No Response")); + done = true; + }) }); } + function generateMessageId(){ + // Use vfs token + return Math.random() + "-" + ++counter; + } + /***** Lifecycle *****/ plugin.on("load", function(){ @@ -55,7 +89,7 @@ define(function(require, exports, module) { }); register(null, { - "bridge-client": plugin + "bridge.client": plugin }); } }); \ No newline at end of file diff --git a/plugins/c9.cli.bridge/bridge-service.js b/plugins/c9.cli.bridge/bridge-service.js index fd6543b9..77e900c7 100644 --- a/plugins/c9.cli.bridge/bridge-service.js +++ b/plugins/c9.cli.bridge/bridge-service.js @@ -1,37 +1,128 @@ module.exports = function (vfs, options, register) { + var stream; + + var net = require("net"); var Stream = require('stream'); - var stream, server; + + var SOCKET = process.platform == "win32" + ? "\\\\.\\pipe\\.c9\\bridge.socket" + : process.env.HOME + "/.c9/bridge.socket"; + + function createListenClient(api){ + var client = net.connect(SOCKET, function(data){ + if (data) api.onData(data); + }); + client.setEncoding("utf8"); + client.unref(); + + client.on("data", function(data){ + if (data) api.onData(data); + }); + + client.on("error", function(err){ + if (err.code == "ECONNREFUSED") { + require("fs").unlink(SOCKET, function(){ + createListenServer(api); + }); + } + else if (err.code == "ENOENT") { + createListenServer(api); + } + else + api.onError(err); + }); + + client.on("end", function(){ + createListenServer(api); + }); + + api.onConnect(client); + + api.disconnect = function(){ + client.end(); + }; + + return client; + } + + function createListenServer(api){ + // var timeout = setTimeout(function(){ + // unixServer.close(); + // }, 500); + + var unixServer = net.createServer(function(client) { + client.setEncoding("utf8"); + + client.on("data", function(data){ + if (data) api.onData(data); + }); + + client.on("error", function(data){ + // console.error("ERROR", api.id, data); + }); + + api.onConnect(client); + }); + unixServer.listen(SOCKET); + + unixServer.on("error", function(err){ + if (err.code == "EADDRINUSE") { + createListenClient(api); + } + else + api.onError(err); + }); + + api.disconnect = function(){ + unixServer.close(); + }; + } register(null, { - connect: function (port, callback) { + connect: function (callback) { if (stream) return callback(null, { stream: stream }); - server = require('net').createServer(function(c) { - var buffer = ""; - c.on("data", function(chunk) { - buffer += chunk; - }); - c.on("end", function(){ - stream.emit("data", buffer); - }); - }); - server.on("error", function(err) { - callback(err); - }); - server.listen(port, process.env.OPENSHIFT_DIY_IP || "localhost", function(err) { - if (err) return callback(err); - callback(null, { stream: stream }); - }); - stream = new Stream(); stream.readable = true; + stream.writable = true; + + var client; + var sent = false; + var api = this.api = { + id: Math.random(), + onConnect: function(c){ + client = c; + if (sent) return; + + callback(null, { stream: stream }); + sent = true; + }, + onData: function(data){ + stream && stream.emit("data", data); + }, + onError: function(err){ + stream && stream.emit("error", err); + } + }; + + // createListenServer + createListenClient(api); + + stream.write = function(data){ + if (client) client.write(data); + }; }, disconnect: function(){ - try { server && server.close(); } + try { this.api && this.api.disconnect(); } catch (e) {} + stream = null; - server = null; + delete this.api; + }, + + destroy: function(){ + this.disconnect(); } }); }; \ No newline at end of file diff --git a/plugins/c9.cli.bridge/bridge.js b/plugins/c9.cli.bridge/bridge.js index bf39c7bc..d1396622 100644 --- a/plugins/c9.cli.bridge/bridge.js +++ b/plugins/c9.cli.bridge/bridge.js @@ -1,4 +1,3 @@ - define(function(require, exports, module) { main.consumes = ["c9", "Plugin", "ext"]; main.provides = ["bridge"]; @@ -8,6 +7,8 @@ define(function(require, exports, module) { var Plugin = imports.Plugin; var c9 = imports.c9; var ext = imports.ext; + + var JSONStream = require("./json-stream"); /***** Initialization *****/ @@ -15,15 +16,10 @@ define(function(require, exports, module) { var emit = plugin.getEmitter(); var ENABLED = options.startBridge !== false; - var PORT = options.port || 17123; var stream, api; - var loaded = false; function load(){ - if (loaded) return; - loaded = true; - if (!ENABLED) return; ext.loadRemotePlugin("bridge", { @@ -35,58 +31,62 @@ define(function(require, exports, module) { api = remote; - api.connect(PORT, function(err, meta) { - if (err) { - loaded = false; + api.connect(function(err, meta) { + if (err) + return console.error(err); // this should never happen - if (err.code == "EADDRINUSE") { - console.warn("Another Application is using port " - + PORT + ". CLI client interface disabled. Restart Cloud9 to retry connecting."); - } - else - console.error(err); - - return; - } - - stream = meta.stream; - - stream.on("data", function(chunk) { - try { var message = JSON.parse(chunk); } - catch (e) { - setTimeout(function(){ - loaded = false; - load(); - }, 60000); - return; - } - emit("message", { message: message }); + stream = new JSONStream(meta.stream); + + stream.on("error", function(err) { + console.error(err); + }); + + stream.on("data", function(payload) { + emit("message", { + message: payload.message, + respond: function(err, message){ + stream.write({ + id: payload.id, + message: message, + error: err + }); + } + }); + }); stream.on("close", function(){ - loaded = false; + load(); }); + + emit.sticky("ready"); }); }); - window.addEventListener("unload", unload); + window.addEventListener("unload", function(){ + api && api.disconnect(); + }); } - function unload() { - api && api.disconnect(); - api = stream = null; - loaded = false; + function write(json){ + if (!stream) { + plugin.once("ready", function(){ write(json); }); + return; + } + + stream.write(json); } - + /***** Methods *****/ plugin.on("load", function(){ c9.on("connect", load, plugin); - c9.on("disconnect", unload, plugin); }); plugin.on("unload", function(){ api && api.disconnect(); + stream = null; + api = null; }); /***** Register and define API *****/ @@ -94,7 +94,12 @@ define(function(require, exports, module) { /** * Bridge To Communicate from CLI to IDE **/ - plugin.freezePublicAPI({ }); + plugin.freezePublicAPI({ + /** + * + */ + write: write + }); register(null, { bridge: plugin diff --git a/plugins/c9.cli.bridge/bridge_commands.js b/plugins/c9.cli.bridge/bridge_commands.js index 496af20a..bf64a756 100644 --- a/plugins/c9.cli.bridge/bridge_commands.js +++ b/plugins/c9.cli.bridge/bridge_commands.js @@ -1,20 +1,25 @@ define(function(require, exports, module) { main.consumes = [ - "Plugin", "bridge", "tabManager", "panels", - "tree.favorites", "tree", "fs" + "Plugin", "bridge", "tabManager", "panels", "tree.favorites", "tree", + "fs", "preferences", "settings", "c9" ]; - main.provides = ["bridge_commands"]; + main.provides = ["bridge.commands"]; return main; function main(options, imports, register) { var Plugin = imports.Plugin; var bridge = imports.bridge; - var tabs = imports.tabManager; + var tabManager = imports.tabManager; var panels = imports.panels; var tree = imports.tree; + var settings = imports.settings; var favs = imports["tree.favorites"]; var fs = imports.fs; + var c9 = imports.c9; + var prefs = imports.preferences; + + var async = require("async"); /***** Initialization *****/ @@ -23,36 +28,62 @@ define(function(require, exports, module) { var BASEPATH = options.basePath; - var loaded = false; function load(){ - if (loaded) return; - loaded = true; - bridge.on("message", function(e) { var message = e.message; switch (message.type) { case "open": - open(message); + open(message, e.respond); break; case "ping": + e.respond(null, true); + break; + default: + console.error("Unknown Bridge Command: ", message.type); break; } - }); + }, plugin); + + settings.on("read", function(e) { + settings.setDefaults("user/terminal", [ + ["defaultEditor", "true"] + ]); + }, plugin); + + prefs.add({ + "Editors" : { + "Terminal" : { + "Use Cloud9 as the Default Editor" : { + type: "checkbox", + path: "user/terminal/@defaultEditor", + position: 14000 + } + } + } + }, plugin); } /***** Methods *****/ - function open(message) { - message.paths.forEach(function(info, i) { + function open(message, callback) { + var i = -1; + var tabs = []; + BASEPATH = c9.toInternalPath(BASEPATH); + + async.each(message.paths, function(info, next) { var path = info.path; + i++; + path = c9.toInternalPath(path); // Make sure file is inside workspace - if (path.substr(0, BASEPATH.length) !== BASEPATH) - return; - - // Remove base path - path = path.substr(BASEPATH.length); + if (path.charAt(0) !== "~") { + if (path.substr(0, BASEPATH.length) !== BASEPATH) + return; // Dont' call callback. Perhaps another client will pick this up. + + // Remove base path + path = path.substr(BASEPATH.length); + } if (info.type == "directory") { path = path.replace(/\/$/, ""); @@ -61,26 +92,45 @@ define(function(require, exports, module) { var node = favs.addFavorite(path); - tree.expand(path, function(err) { + tree.expand(path, function() { tree.select(node); //path || "/"); tree.scrollToSelection(); + next(); }); tree.focus(); } else { - tabs.once("ready", function(){ + tabManager.once("ready", function(){ fs.exists(path, function(existing) { - tabs.open({ + var tab = tabManager.open({ path: path, active: i === 0, document: existing ? undefined : { meta : { newfile: true } } - }, function(){}); + }, function(){ + next(); + }); + + if (message.wait) { + tab.on("close", function(){ + tabs.splice(tabs.indexOf(tab), 1); + if (!tabs.length) + callback(null, true); + }); + } + + tabs.push(tab); }); }); } + }, function(err){ + if (err) + return callback(err); + + if (!message.wait || !tabs.length) + callback(null, true); }); } @@ -102,7 +152,7 @@ define(function(require, exports, module) { plugin.freezePublicAPI({}); register(null, { - "bridge_commands": plugin + "bridge.commands": plugin }); } }); diff --git a/plugins/c9.cli.bridge/bridge_test.js b/plugins/c9.cli.bridge/bridge_test.js index 253d9cca..ccb93842 100644 --- a/plugins/c9.cli.bridge/bridge_test.js +++ b/plugins/c9.cli.bridge/bridge_test.js @@ -2,7 +2,7 @@ "use client"; -require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) { +require(["lib/architect/architect", "lib/chai/chai", "/vfs-root", "/vfs-home"], function (architect, chai, basePath, homePath) { var expect = chai.expect; var Assert = chai.assert; @@ -14,46 +14,70 @@ require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) debug: true, hosted: true, local: false, - davPrefix: "/" + davPrefix: "/", + home: homePath }, "plugins/c9.core/ext", "plugins/c9.core/http-xhr", "plugins/c9.core/util", + "plugins/c9.ide.ui/lib_apf", "plugins/c9.ide.ui/ui", "plugins/c9.core/settings", - //"plugins/c9.ide.collab/collab", "plugins/c9.vfs.client/vfs_client", "plugins/c9.vfs.client/endpoint", "plugins/c9.ide.auth/auth", "plugins/c9.fs/fs", + "plugins/c9.fs/net", + + { + packagePath: "plugins/c9.cli.bridge/bridge", + startBridge: true + }, + { + packagePath: "plugins/c9.cli.bridge/bridge_commands", + basePath: basePath + }, + "plugins/c9.cli.bridge/bridge-client", // Mock plugins { - consumes: ["ui"], + consumes: [], provides: [ - "preferences", "dialog.error" + "preferences", "ui" ], setup: expect.html.mocked }, { - consumes: ["collab"], + consumes: ["bridge", "bridge.client"], provides: [], setup: main } ], architect); function main(options, imports, register) { - var collab = imports.collab; + var bridge = imports.bridge; + var client = imports["bridge.client"]; - describe('collab', function() { - this.timeout(10000); + describe('bridge', function() { + // this.timeout(10000); - describe("connect", function(){ - it('should connect', function(done) { - collab.connect(null, function(err, stream) { - if (err) throw err.message; - }); + before(function(done){ + bridge.on("ready", function(){ + done(); + }); + }); + + it('send and receive messages', function(done) { + bridge.on("message", function(e){ + if (e.message.hello) { + e.respond(null, { "hi": true }); + } + }); + client.send({ "hello": true }, function(err, message){ + if (err) throw err.message; + expect(message).property("hi").to.be.ok; + done(); }); }); }); diff --git a/plugins/c9.cli.bridge/json-stream.js b/plugins/c9.cli.bridge/json-stream.js new file mode 100644 index 00000000..1f48ed05 --- /dev/null +++ b/plugins/c9.cli.bridge/json-stream.js @@ -0,0 +1,47 @@ +define(function(require, exports, module) { + +var EventEmitter = require("events").EventEmitter; + +module.exports = function(stream) { + var emit = this.emit.bind(this); + + var buffer = ""; + stream.on("data", function(chunk) { + buffer += chunk; + + var parts = buffer.split("\n"); + while (parts.length) { + try { + var message = JSON.parse(parts[0]); + emit("data", message); + parts.shift(); + } + catch (e) { + if (parts.length !== 1) { + emit("error", e); + parts.shift(); + } + else { + break; + } + } + } + buffer = parts.join("\n"); + }); + + stream.on("error", function(err){ + emit("error", err); + }); + + stream.on("close", function(data){ + emit("close", data); + }); + + this.write = function(data) { + stream.write(JSON.stringify(data) + "\n"); + }; +}; + +module.exports.prototype = new EventEmitter(); + +}); \ No newline at end of file diff --git a/plugins/c9.cli.open/open.js b/plugins/c9.cli.open/open.js index 0ef32d02..d186f500 100755 --- a/plugins/c9.cli.open/open.js +++ b/plugins/c9.cli.open/open.js @@ -1,5 +1,5 @@ define(function(require, exports, module) { - main.consumes = ["Plugin", "cli_commands", "proc", "bridge-client"]; + main.consumes = ["Plugin", "cli_commands", "proc", "bridge.client"]; main.provides = ["open"]; return main; @@ -7,7 +7,7 @@ define(function(require, exports, module) { var Plugin = imports.Plugin; var cmd = imports.cli_commands; var proc = imports.proc; - var bridge = imports["bridge-client"]; + var bridge = imports["bridge.client"]; var fs = require("fs"); var PATH = require("path"); @@ -25,11 +25,12 @@ define(function(require, exports, module) { cmd.addCommand({ name: "open", info: " Opens a file or directory.", - usage: "", + usage: "[--wait] ", options: { - "path" : { - description: "Specify the path that will be opened", - default: false + "wait": { + description: "Wait until the file(s) are closed", + "default": false, + "boolean": true } }, check: function(argv) { @@ -39,6 +40,7 @@ define(function(require, exports, module) { exec: function(argv) { open( argv._.slice(1), // Remove "open" from the paths + argv.wait, function(){}); } }); @@ -46,12 +48,16 @@ define(function(require, exports, module) { /***** Methods *****/ - function open(paths, callback) { + function open(paths, wait, callback) { try { paths = paths.map(function(path) { var isDir = fs.existsSync(path) && fs.statSync(path).isDirectory(); + path = PATH.resolve(path); + if (path.substr(0, process.env.HOME.length) == process.env.HOME) + path = "~" + path.substr(process.env.HOME.length); + return { - path: "/" + PATH.resolve(path), + path: path, type: isDir ? "directory" : "file" }; }); @@ -65,6 +71,7 @@ define(function(require, exports, module) { paths.forEach(function(info) { var path = info.type == "directory" ? info.path : PATH.dirname(info.path); + if (!last) { last = path; } @@ -86,11 +93,12 @@ define(function(require, exports, module) { var message = { type: "open", workspace: "local", + wait: wait, // cwd : cwd, paths: paths }; - bridge.send(message, function cb(err) { + bridge.send(message, function cb(err, response) { if (err) { if (err.code == "ECONNREFUSED") { // Seems Cloud9 is not running, lets start it up @@ -111,6 +119,9 @@ define(function(require, exports, module) { console.log(err.message); } + if (response !== true) + console.log("Could not open ", paths); + process.exit(); // I don't get why this is needed }); } @@ -129,13 +140,16 @@ define(function(require, exports, module) { var timed = Date.now(); (function retry(){ - bridge.send({ type: "ping" }, function(err) { + bridge.send({ type: "ping" }, function(err, message) { if (!err) return callback(true); if (Date.now() - timed > 10000) return callback(false); + if (message !== true) + return callback(false); + setTimeout(retry, 100); }); })(); diff --git a/plugins/c9.cli.publish/install.js b/plugins/c9.cli.publish/install.js index b1643e22..ce82d09c 100644 --- a/plugins/c9.cli.publish/install.js +++ b/plugins/c9.cli.publish/install.js @@ -77,10 +77,6 @@ define(function(require, exports, module) { "default": false, "boolean": true }, - "package" : { - description: "", - "default": false - }, "verbose" : { "description": "Output more information", "alias": "v", @@ -131,7 +127,7 @@ define(function(require, exports, module) { process.exit(1); } else { - console.log("Succesfully installed", name + (argv.debug ? "" : "@" + data.version)); + console.log("Successfully installed", name + (argv.debug ? "" : "@" + data.version)); process.exit(0); } }); @@ -140,6 +136,7 @@ define(function(require, exports, module) { cmd.addCommand({ name: "remove", + alias: "uninstall", info: " Removes a cloud9 package.", usage: "[--verbose] [--global] [--local] ", // @TODO --global options: { @@ -153,9 +150,6 @@ define(function(require, exports, module) { "default": false, "boolean": true }, - "package" : { - description: "" - }, "verbose" : { "description": "Output more information", "alias": "v", @@ -186,7 +180,7 @@ define(function(require, exports, module) { process.exit(1); } else { - console.log("Succesfully removed", name); + console.log("Successfully removed", name); process.exit(0); } }); @@ -387,8 +381,8 @@ define(function(require, exports, module) { if (verbose) console.log("Installing debug version of package"); - if (!options.test) - return callback(new Error("Dry run is not supported for debug installations")); + if (options.test) + return callback(new Error("Test is not supported for debug installations")); prepareDirectory(function(err, packagePath){ if (err) return callback(err); diff --git a/plugins/c9.cli.publish/publish.js b/plugins/c9.cli.publish/publish.js index 8bb04348..e79214bd 100644 --- a/plugins/c9.cli.publish/publish.js +++ b/plugins/c9.cli.publish/publish.js @@ -88,8 +88,7 @@ define(function(require, exports, module) { } }, check: function(argv) { - // if (argv._.length < 2 && !argv["newversion"] && !argv["dry-run"]) - // throw new Error("Missing version"); + }, exec: function(argv) { verbose = argv["verbose"]; @@ -438,9 +437,9 @@ define(function(require, exports, module) { var path = join(cwd, json.installer); var installerCode = fs.readFileSync(path, "utf8"); - var m = installerCode.match(/\.version\s*=\s*(\d+)/g); + var m = installerCode.match(/\.version\s*=\s*(\d+)/); - var installerVersion = m && m[0]; + var installerVersion = m && m[1]; if (!installerVersion) return callback(new Error("ERROR: missing installer version in " + json.installer)); extraCode.push({ @@ -724,7 +723,7 @@ define(function(require, exports, module) { request.on('response', function(res) { // TODO better handle version exists error - if (res.statusCode == 412 && !version) + if (res.statusCode == 412) console.error("ERROR: most likely version " + json.version + " already exisits, try increasing version"); if (res.statusCode != 200) return callback(new Error("ERROR: Unknown Error:" + res.statusCode)); diff --git a/plugins/c9.cli/auth.bootstrap.js b/plugins/c9.cli/auth.bootstrap.js index a24fd471..684225f1 100644 --- a/plugins/c9.cli/auth.bootstrap.js +++ b/plugins/c9.cli/auth.bootstrap.js @@ -66,7 +66,6 @@ define(function(require, exports, module) { }, function(err, token) { if (err) return callback(err); - fs.writeFile(AUTHPATH, token, function(err){ if (err) return callback(err); callback(null, lastToken = token); diff --git a/plugins/c9.cli/cli.js b/plugins/c9.cli/cli.js index 4a5923fc..6c0ec08a 100755 --- a/plugins/c9.cli/cli.js +++ b/plugins/c9.cli/cli.js @@ -7,6 +7,9 @@ define(function(require, exports, module) { var Plugin = imports.Plugin; var cmd = imports.cli_commands; + var fs = require("fs"); + var resolve = require("path").resolve; + var optimist; /***** Initialization *****/ @@ -21,7 +24,7 @@ define(function(require, exports, module) { var module; var argv; - process.argv.slice(2).some(function(n){ + process.argv.slice(2).some(function(n) { if (!n.match(/^[-\/]/) && n != "node") { module = n; return true; @@ -29,6 +32,18 @@ define(function(require, exports, module) { return false; }); + if (!commands[module] && process.argv.length > 2) { + for (var i = 2; i < process.argv.length; i++) { + if (process.argv[i].charAt(0) == "-") continue; + var path = resolve(process.argv[i]); + if (fs.existsSync(path)) { + process.argv.splice(2, 0, "open"); + module = "open"; + } + break; + } + } + optimist = require('optimist'); if (!module || !commands[module]) { @@ -62,6 +77,12 @@ define(function(require, exports, module) { argv = optimist .usage("The Cloud9 CLI.\nUsage: c9 " + module + " [--help] " + def.usage) .options(def.options); + + if (argv.argv.help) + argv = argv.check(function(){ + if (argv.help) + throw new Error("Help Requested"); + }); if (def.check) argv = argv.check(def.check); argv = argv.argv; diff --git a/plugins/c9.fs/fs.js b/plugins/c9.fs/fs.js index a3145076..376f68fb 100644 --- a/plugins/c9.fs/fs.js +++ b/plugins/c9.fs/fs.js @@ -55,7 +55,7 @@ define(function(require, exports, module) { loaded = true; if (options.cli) - plugin.on("error", function(e){ console.error(e.error); }); + plugin.on("error", function(e){ }); // Prevent exception } function wrap(name, fn) { diff --git a/plugins/c9.ide.ace/ace.js b/plugins/c9.ide.ace/ace.js index 4609d862..a06b159e 100644 --- a/plugins/c9.ide.ace/ace.js +++ b/plugins/c9.ide.ace/ace.js @@ -40,13 +40,14 @@ define(function(require, exports, module) { var lang = require("ace/lib/lang"); var Range = require("ace/range").Range; var config = require("ace/config"); - var AceEditor = require("ace/editor").Editor; var Document = require("ace/document").Document; + var AceEditor = require("ace/editor").Editor; var EditSession = require("ace/edit_session").EditSession; + var UndoManager = require("ace/undomanager").UndoManager; + var whitespaceUtil = require("ace/ext/whitespace"); var defaultCommands = require("ace/commands/default_commands").commands; var VirtualRenderer = require("ace/virtual_renderer").VirtualRenderer; var multiSelectCommands = require("ace/multi_select").commands; - var whitespaceUtil = require("ace/ext/whitespace"); // enable multiselect require("ace/multi_select"); @@ -281,100 +282,169 @@ define(function(require, exports, module) { /***** Undo Manager *****/ function AceUndoManager(undoManager, session) { + var state = undoManager.getState(); this.$session = session; this.$undo = undoManager; - var _self = this; - var Item = this.Item; - this.$undo.on("itemFind", function(e) { - return Item(_self, e.state); - }); + this.$aceUndo = new UndoManager(); + this.$aceUndo.c9UndoProxy = undoManager; + undoManager.$aceUndo = this.$aceUndo; + undoManager.add = this.add; + undoManager.addSelection = this.addSelection; + undoManager.undo = this.undo; + undoManager.redo = this.redo; + undoManager.reset = this.reset; + undoManager.canUndo = this.canUndo; + undoManager.canRedo = this.canRedo; + undoManager.getState = this.getState; + undoManager.setState = this.setState; + undoManager.bookmark = this.bookmarkPosition; + undoManager.isAtBookmark = this.isAtBookmark; + undoManager.__defineGetter__("position", this.getPosition); + undoManager.__defineGetter__("length", this.getLength); + undoManager._emit = this._emit = undoManager.getEmitter(); + + this.deleyedEmit = lang.delayedCall(this._emit.bind(null, "change")) + .schedule.bind(null, 0); + this.setState(state, true); + } + function updateDeltas(deltas) { + if (deltas[0] && deltas[0].deltas) { + var oldDeltas = deltas.slice(); + deltas.length = 0; + oldDeltas.forEach(function(x) { + deltas.push.apply(deltas, x.deltas); + }); + } + return deltas; } AceUndoManager.prototype = { - Item: function(_self, deltas) { - return { - undo: function(){ - _self.$session.session.undoChanges(deltas, _self.dontSelect); - }, - redo: function(){ - _self.$session.session.redoChanges(deltas, _self.dontSelect); - }, - getState: function(){ - return deltas.filter(function (d) { - return d.group != "fold"; - }); - } - }; + add: function(delta, doc) { + this.$aceUndo.add(delta, doc); + this._emit("change"); }, - - execute: function(options) { - if (options.merge && this.lastDeltas) { - this.lastDeltas.push.apply(this.lastDeltas, options.args[0]); - } else { - this.lastDeltas = options.args[0]; - this.$undo.add(this.Item(this, this.lastDeltas)); - } + addSelection: function(range, rev) { + this.$aceUndo.addSelection(range, rev); }, - undo: function(dontSelect) { - this.dontSelect = dontSelect; - this.$undo.undo(); + this.$aceUndo.undo(dontSelect); + this._emit("change"); }, redo: function(dontSelect) { - this.dontSelect = dontSelect; - this.$undo.redo(); + this.$aceUndo.redo(dontSelect); + this._emit("change"); }, reset: function(){ - this.$undo.reset(); + this.$aceUndo.reset(); + this._emit("change"); }, - hasUndo: function() { - return this.$undo.length > this.$undo.position + 1; + canUndo: function() { + return this.$aceUndo.canUndo(); }, - hasRedo: function() { - return this.$undo.length <= this.$undo.position + 1; + canRedo: function() { + return this.$aceUndo.canRedo(); }, - get $undoStack() { - return this.$undo.stack.slice(0, this.$undo.position + 1) - .map(function(e){ return e.getState ? e.getState() : e }); + clearUndo: function() { + this.$aceUndo.$undoStack = []; + this._emit("change"); + }, + clearRedo: function() { + this.$aceUndo.$redoStack = []; + this._emit("change"); + }, + startNewGroup: function() { + return this.$aceUndo.startNewGroup(); + }, + markIgnored: function(from, to) { + return this.$aceUndo.markIgnored(from, to); + }, + getState: function() { + var aceUndo = this.$aceUndo; + var mark = -1; + var aceMark = aceUndo.mark; + var stack = []; + function transform(deltaSet) { + var newDelta = deltaSet.filter(function (d) { + if (d.id == aceMark) mark = stack.length; + return d.action == "insert" || d.action == "remove"; + }); + stack.push(newDelta); + } + aceUndo.$undoStack.forEach(transform); + if (aceUndo.$redoStackBaseRev == aceUndo.$rev) + aceUndo.$redoStack.forEach(transform); + return { + stack: stack, + mark: mark, + position: aceUndo.$undoStack.length - 1 + }; + }, + setState: function(e, silent) { + var aceUndo = this.$aceUndo; + var stack = e.stack || []; + var marked = stack[e.mark] && stack[e.mark][0]; + var pos = e.position + 1; + var undo = stack.slice(0, pos); + var redo = stack.slice(pos); + aceUndo.$undoStack = undo.filter(function(x) { + return x.length; + }).map(updateDeltas); + aceUndo.$redoStack = redo.filter(function(x) { + return x.length; + }).map(updateDeltas); + stack = aceUndo.$undoStack; + var lastDeltaGroup = stack[stack.length - 1]; + var lastRev = lastDeltaGroup && lastDeltaGroup[0].id || 0; + aceUndo.$rev = lastRev; + aceUndo.$redoStackBaseRev = aceUndo.$rev; + aceUndo.$maxRev = Math.max(aceUndo.$maxRev, lastRev); + var markedRev = marked && marked.id; + if (markedRev != null) + this.$aceUndo.bookmark(markedRev); + else if (e.mark == e.position) + this.$aceUndo.bookmark(); + else + this.$aceUndo.bookmark(-1); + silent || this._emit("change"); + }, + isAtBookmark: function() { + return this.$aceUndo.isAtBookmark(); + }, + bookmark: function(rev) { + this.$aceUndo.bookmark(rev); + this._emit("change"); + }, + bookmarkPosition: function(index) { + if (index > -1) { + var stack = this.$aceUndo.$undoStack; + if (index >= stack.length) { + index -= stack.length; + stack = this.$aceUndo.$redoStack; + index = stack.length - index; + } + var deltaSet = stack[index]; + var rev = deltaSet && deltaSet[0] && deltaSet[0].id; + if (rev == null) rev = -1; + this.$aceUndo.bookmark(rev); + } else if (index == -1) { + this.$aceUndo.bookmark(0); + } else { + this.$aceUndo.bookmark(index); + } + this._emit("change"); + }, + addSession: function(session) { + this.$aceUndo.addSession(session); + }, + getPosition: function() { + var aceUndo = this.$aceUndo; + return aceUndo.$undoStack.length - 1; + }, + getLength: function() { + var aceUndo = this.$aceUndo; + return aceUndo.$undoStack.length + aceUndo.$redoStack.length; } }; - function UndoManagerProxy(undoManager, session) { - this.$u = undoManager; - this.$doc = session; - } - - (function() { - this.execute = function(options) { - this.$u.execute(options); - }; - - this.undo = function() { - var selectionRange = this.$u.undo(true); - if (selectionRange) { - this.$doc.selection.setSelectionRange(selectionRange); - } - }; - - this.redo = function() { - var selectionRange = this.$u.redo(true); - if (selectionRange) { - this.$doc.selection.setSelectionRange(selectionRange); - } - }; - - this.reset = function() { - this.$u.reset(); - }; - - this.hasUndo = function() { - return this.$u.hasUndo(); - }; - - this.hasRedo = function() { - return this.$u.hasRedo(); - }; - }).call(UndoManagerProxy.prototype); - /***** Generic Load *****/ handle.on("load", function(){ @@ -1370,8 +1440,7 @@ define(function(require, exports, module) { if (!undoManager) undoManager = session.getUndoManager(); if (undoManager) { - var undoManagerProxy = new UndoManagerProxy(undoManager, s); - s.setUndoManager(undoManagerProxy); + s.setUndoManager(undoManager); } // Overwrite the default $informUndoManager function such that new deltas @@ -2172,9 +2241,9 @@ define(function(require, exports, module) { var c9Session = doc.getSession(); // if load starts from another editor type - // tabmanager will show as instantly + // tabmanager will show us instantly // so we need to show progress bar instantly - progress.noFadeIn = !currentDocument; + progress.noFadeIn = !currentDocument || !currentDocument.tab.active; // Value Retrieval doc.on("getValue", function get(e) { diff --git a/plugins/c9.ide.auth/auth.js b/plugins/c9.ide.auth/auth.js index 8508ba0f..54fd2633 100644 --- a/plugins/c9.ide.auth/auth.js +++ b/plugins/c9.ide.auth/auth.js @@ -80,7 +80,10 @@ define(function(require, exports, module) { request(apiUrl + "/user", function(err, user) { if (err || !user) { - console.warn("LOGIN: API /user err", err); + if (options.cli) + console.warn("Invalid username or password. Please try again."); + else + console.warn("LOGIN: API /user err", err); return setTimeout(login, 1000); } diff --git a/plugins/c9.ide.dialog.common/alert_internal.js b/plugins/c9.ide.dialog.common/alert_internal.js index 2b527375..c76edc94 100644 --- a/plugins/c9.ide.dialog.common/alert_internal.js +++ b/plugins/c9.ide.dialog.common/alert_internal.js @@ -1,11 +1,12 @@ define(function(require, module, exports) { - main.consumes = ["Dialog", "util", "dialog.alert"]; + main.consumes = ["Dialog", "util", "dialog.alert", "metrics"]; main.provides = ["dialog.alert_internal"]; return main; function main(options, imports, register) { var Dialog = imports.Dialog; var util = imports.util; + var metrics = imports.metrics; var alertWrapper = imports["dialog.alert"]; /***** Initialization *****/ @@ -25,6 +26,8 @@ define(function(require, module, exports) { /***** Methods *****/ function show(title, header, msg, onhide, options) { + metrics.increment("dialog.error"); + return plugin.queue(function(){ if (header === undefined) { plugin.title = "Notice"; diff --git a/plugins/c9.ide.dialog.common/error.js b/plugins/c9.ide.dialog.common/error.js index 7a6cfde5..4b4142e6 100644 --- a/plugins/c9.ide.dialog.common/error.js +++ b/plugins/c9.ide.dialog.common/error.js @@ -1,13 +1,14 @@ define(function(require, exports, module) { "use strict"; - main.consumes = ["Plugin", "ui"]; + main.consumes = ["Plugin", "ui", "metrics"]; main.provides = ["dialog.error"]; return main; function main(options, imports, register) { var Plugin = imports.Plugin; var ui = imports.ui; + var metrics = imports.metrics; /***** Initialization *****/ @@ -84,6 +85,8 @@ define(function(require, exports, module) { } function show(message, timeout) { + metrics.increment("dialog.error"); + // Error message container if (!error) { error = document.body.appendChild(document.createElement("div")); diff --git a/plugins/c9.ide.editors/document.js b/plugins/c9.ide.editors/document.js index c305ad8f..6c7174c5 100644 --- a/plugins/c9.ide.editors/document.js +++ b/plugins/c9.ide.editors/document.js @@ -43,7 +43,7 @@ define(function(require, module, exports) { function initUndo(){ undoManager.on("change", function(e) { var c = !undoManager.isAtBookmark(); - if (changed !== c || undoManager.position == -1) { + if (changed !== c) { changed = c; emit("changed", { changed: c }); } @@ -60,26 +60,24 @@ define(function(require, module, exports) { } var state = getState(); - undoManager.once("change", function(){ - // Bookmark the undo manager - undoManager.bookmark(); - - // Update state - delete state.changed; - delete state.value; - delete state.meta; - state.undoManager = undoManager.getState(); - - if (cleansed && editor && state[editor.type]) - state[editor.type].cleansed = true; - - // Set new state (preserving original state) - if (emit("mergeState") !== false) - setState(state); - }); - // Record value (which should add an undo stack item) plugin.value = value; + + // Bookmark the undo manager + undoManager.bookmark(); + + // Update state + delete state.changed; + delete state.value; + delete state.meta; + state.undoManager = undoManager.getState(); + + if (cleansed && editor && state[editor.type]) + state[editor.type].cleansed = true; + + // Set new state (preserving original state) + if (emit("mergeState") !== false) + setState(state); } function getState(filter) { diff --git a/plugins/c9.ide.editors/undomanager.js b/plugins/c9.ide.editors/undomanager.js index 6ab1adc9..db1e90a0 100644 --- a/plugins/c9.ide.editors/undomanager.js +++ b/plugins/c9.ide.editors/undomanager.js @@ -10,7 +10,7 @@ define(function(require, module, exports) { var plugin = new Plugin("Ajax.org", main.consumes); var emit = plugin.getEmitter(); - var position = -1, mark = null, stack = []; + var position = -1, mark = -2, stack = []; if (options) setState(options); @@ -55,7 +55,7 @@ define(function(require, module, exports) { position = 0; if (mark < position) - mark = -1; + mark = -2; emit("change"); } @@ -64,7 +64,7 @@ define(function(require, module, exports) { stack = stack.slice(0, position + 1); if (mark > position) - mark = -1; + mark = -2; if (!noEvent) emit("change"); @@ -91,7 +91,7 @@ define(function(require, module, exports) { position--; if (mark == idx) - mark = -1; + mark = -2; else if (mark > idx) mark--; @@ -105,8 +105,7 @@ define(function(require, module, exports) { } function isAtBookmark(){ - return mark !== null && mark == position - || mark === null && position == -1; + return mark == position; } function item(idx) { @@ -133,7 +132,7 @@ define(function(require, module, exports) { return; // guard against broken stack stack = state.stack; - emit("change"); //If you remove this again, change the test + emit("change"); // If you remove this again, change the test } function findItem(compressedItem) { @@ -146,11 +145,13 @@ define(function(require, module, exports) { position = -1; stack = []; - mark = null; + mark = -1; emit("change"); } + plugin.freezePublicAPI.baseclass(); + /** * The Undo Manager class of Cloud9. Each {@link Document} * has a single instance of the undo manager that @@ -213,10 +214,6 @@ define(function(require, module, exports) { * **/ plugin.freezePublicAPI({ - /** - * @ignore - */ - get stack() { return stack; }, /** * The number of items on the stack. This number will stay the * same when using {@link UndoManager#undo} and diff --git a/plugins/c9.ide.editors/undomanager_test.js b/plugins/c9.ide.editors/undomanager_test.js index b7624924..a3c57ada 100644 --- a/plugins/c9.ide.editors/undomanager_test.js +++ b/plugins/c9.ide.editors/undomanager_test.js @@ -133,7 +133,7 @@ require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) expect(undo.position).to.equal(4); undo.undo(); check++; - undo.setState({ position : -1, stack : stack, mark : null }); check++; + undo.setState({ position : -1, stack : stack, mark : -1 }); check++; expect(undo.isAtBookmark()).to.equal(true); expect(data).to.deep.equal(["a", "q"]); checkCount(); diff --git a/plugins/c9.ide.errorhandler/raygun_error_handler.js b/plugins/c9.ide.errorhandler/raygun_error_handler.js index 3c3dcca8..82ddc557 100644 --- a/plugins/c9.ide.errorhandler/raygun_error_handler.js +++ b/plugins/c9.ide.errorhandler/raygun_error_handler.js @@ -8,7 +8,7 @@ define(function(require, exports, module) { "use strict"; main.consumes = [ - "Plugin", "info" + "Plugin", "info", "metrics" ]; main.provides = ["error_handler"]; return main; @@ -16,6 +16,7 @@ define(function(require, exports, module) { function main(options, imports, register) { var Plugin = imports.Plugin; var info = imports.info; + var metrics = imports.metrics; /***** Initialization *****/ @@ -57,7 +58,8 @@ define(function(require, exports, module) { Raygun.setVersion(version + ".0"); } - function reportError(exception, customData, tags) { + function log(exception, customData, tags) { + metrics.increment("errorhandler.log"); if (typeof exception === "string") exception = new Error(exception); if (!exception) @@ -78,8 +80,8 @@ define(function(require, exports, module) { plugin.freezePublicAPI({ /** @deprecated Use log() instead. */ - reportError: reportError, - log: reportError + reportError: log, + log: log }); register(null, { "error_handler" : plugin }); diff --git a/plugins/c9.ide.plugins/installer.js b/plugins/c9.ide.plugins/installer.js index 2e102bae..152e1824 100644 --- a/plugins/c9.ide.plugins/installer.js +++ b/plugins/c9.ide.plugins/installer.js @@ -1,6 +1,6 @@ define(function(require, exports, module) { main.consumes = [ - "Plugin", "proc", "c9", "pubsub", "auth", "util" + "Plugin", "proc", "c9", "pubsub", "auth", "util", "installer" ]; main.provides = ["plugin.installer"]; return main; @@ -12,6 +12,9 @@ define(function(require, exports, module) { var proc = imports.proc; var auth = imports.auth; var pubsub = imports.pubsub; + var installer = imports.installer; + + var async = require("async"); var escapeShell = util.escapeShell; var updates = options.updates; @@ -20,11 +23,10 @@ define(function(require, exports, module) { /***** Initialization *****/ var plugin = new Plugin("Ajax.org", main.consumes); - // var emit = plugin.getEmitter(); + var emit = plugin.getEmitter(); var HASSDK = c9.location.indexOf("sdk=0") === -1; - var queue = []; var installing; var loaded = false; @@ -69,87 +71,66 @@ define(function(require, exports, module) { // return; // } - if (!config.length) return; + if (!config.length) + return callback && callback(); - var found = {}; - config.forEach(function(item){ - if (!found[item.packageName]) - found[item.packageName] = true; - else return; - - queue.push({ name: item.packageName, version: item.version }); - - if (installing) - installing.push(item); - }); - - if (installing) return; - installing = config; - - var i = 0; - function next(err){ - if (err) console.log(err); - - if (!queue[i]) { - installing = false; queue = []; - architect.loadAdditionalPlugins(config, callback); - return; - } - - installPlugin(queue[i].name, queue[i].version, next); - i++; + // Only run one installer at a time + if (installing) { + return plugin.once("finished", function(){ + installPlugins(config, callback); + }); } - next(); + installing = true; + + var found = {}, packages = []; + config.forEach(function(item){ + if (!found[item.name]) + found[item.name] = true; + else return; + + packages.push({ name: item.name, version: item.version }); + }); + + async.eachSeries(packages, function(pkg, next){ + installPlugin(pkg.name, pkg.version, next); + }, function(err){ + installing = false; + emit("finished"); + + if (err) { + console.error(err.message); + return callback && callback(err); + } + + architect.loadAdditionalPlugins(config, callback); + }); } function installPlugin(name, version, callback){ - proc.spawn("bash", { - args: ["-c", ["c9", "install", "--local", "--force", "--accessToken=" + auth.accessToken, escapeShell(name) + "@" + escapeShell(version)].join(" ")] - }, function(err, process){ - if (err) return callback(err); - - process.stdout.on("data", function(c){ - console.log(c); - }); - process.stderr.on("data", function(c){ - console.error(c); + // Headless installation of the plugin + installer.createSession(name, version, function(session, options){ + session.install({ + "bash": "c9 install --local --force --accessToken=" + auth.accessToken + + " " + escapeShell(name) + "@" + escapeShell(version) }); - process.on("exit", function(code){ - if (code) { - var error = new Error(err); - error.code = code; - return callback(error); - } - callback(); - }); - }); + // Force to start immediately + session.start(callback, true); + }, function(){}, 2); // Force to not be administered } function uninstallPlugin(name, callback){ - proc.spawn("c9", { - args: ["remove", "--local", "--force", "--accessToken=" + auth.accessToken, escapeShell(name)] - }, function(err, process){ - if (err) return callback(err); - - var res = null; - process.stdout.on("data", function(c){ - res = c.toString("utf8"); - }); - process.stderr.on("data", function(c){ - err = c.toString("utf8"); + // Headless uninstallation of the plugin + installer.createSession(name, -1, function(session, options){ + session.install({ + "bash": "c9 remove --local --force --accessToken=" + auth.accessToken + + " " + escapeShell(name) }); - process.on("exit", function(code){ - if (code) { - var error = new Error(err); - error.code = code; - return callback(error); - } - callback(null, res); - }); - }); + // Force to start immediately + session.start(callback, true); + }, function(){}, 2); // Force to not be administered } /***** Lifecycle *****/ @@ -160,7 +141,6 @@ define(function(require, exports, module) { plugin.on("unload", function() { loaded = false; installing = false; - queue = []; }); /***** Register and define API *****/ @@ -180,6 +160,11 @@ define(function(require, exports, module) { */ installPlugins: installPlugins, + /** + * + */ + installPlugin: installPlugin, + /** * */ diff --git a/plugins/c9.ide.plugins/manager.js b/plugins/c9.ide.plugins/manager.js index 77294880..63c96f60 100644 --- a/plugins/c9.ide.plugins/manager.js +++ b/plugins/c9.ide.plugins/manager.js @@ -85,7 +85,9 @@ define(function(require, exports, module) { }; var TEMPLATES = { "plugin.simple": "Empty Plugin", - "plugin.default": "Full Plugin" + "plugin.default": "Full Plugin", + "plugin.installer": "Installer Plugin", + "plugin.bundle": "Cloud9 Bundle" }; // @TODO add sorting @@ -102,7 +104,6 @@ define(function(require, exports, module) { // var emit = plugin.getEmitter(); var HASSDK = c9.location.indexOf("sdk=0") === -1; - var ENABLED = c9.location.indexOf("sdk=1") > -1; var model, datagrid, filterbox; var btnUninstall, btnReport, btnReadme, btnCloud9, btnReload; @@ -125,20 +126,18 @@ define(function(require, exports, module) { // updateCommandsFromSettings(); // }, plugin); - if (ENABLED) { - menus.addItemByPath("File/New Plugin", null, 210, plugin); - Object.keys(TEMPLATES).forEach(function(name){ - menus.addItemByPath("File/New Plugin/" + TEMPLATES[name], new ui.item({ - onclick: function(){ - createNewPlugin(name); - } - }), 210, plugin); - }); - - ext.on("register", function(){ - setTimeout(reloadModel); - }); - } + menus.addItemByPath("File/New Plugin", null, 210, plugin); + Object.keys(TEMPLATES).forEach(function(name){ + menus.addItemByPath("File/New Plugin/" + TEMPLATES[name], new ui.item({ + onclick: function(){ + createNewPlugin(name); + } + }), 210, plugin); + }); + + ext.on("register", function(){ + setTimeout(reloadModel); + }); } var drawn; diff --git a/plugins/c9.ide.plugins/mock/c9.ide.example3/plugin.html b/plugins/c9.ide.plugins/mock/c9.ide.example3/plugin.html new file mode 100644 index 00000000..d8688f81 --- /dev/null +++ b/plugins/c9.ide.plugins/mock/c9.ide.example3/plugin.html @@ -0,0 +1 @@ +
Hello World
\ No newline at end of file diff --git a/plugins/c9.ide.plugins/mock/c9.ide.example3/plugin.js b/plugins/c9.ide.plugins/mock/c9.ide.example3/plugin.js new file mode 100644 index 00000000..3dca2f71 --- /dev/null +++ b/plugins/c9.ide.plugins/mock/c9.ide.example3/plugin.js @@ -0,0 +1,155 @@ +define(function(require, exports, module) { + main.consumes = [ + "Plugin", "ui", "commands", "menus", "preferences", "settings" + ]; + main.provides = ["myplugin"]; + return main; + + function main(options, imports, register) { + var Plugin = imports.Plugin; + var ui = imports.ui; + var menus = imports.menus; + var commands = imports.commands; + var settings = imports.settings; + var prefs = imports.preferences; + + /***** Initialization *****/ + + var plugin = new Plugin("Ajax.org", main.consumes); + var emit = plugin.getEmitter(); + + var showing; + function load() { + commands.addCommand({ + name: "mycommand", + bindKey: { mac: "Command-I", win: "Ctrl-I" }, + isAvailable: function(){ return true; }, + exec: function() { + showing ? hide() : show(); + } + }, plugin); + + menus.addItemByPath("Tools/My Menu Item", new ui.item({ + command: "mycommand" + }), 300, plugin); + + settings.on("read", function(e){ + settings.setDefaults("user/my-plugin", [ + ["first", "1"], + ["second", "all"] + ]); + }); + + prefs.add({ + "Example" : { + position: 450, + "My Plugin" : { + position: 100, + "First Setting": { + type: "checkbox", + path: "user/my-plugin/@first", + position: 100 + }, + "Second Setting": { + type: "dropdown", + path: "user/my-plugin/@second", + width: "185", + position: 200, + items: [ + { value: "you", caption: "You" }, + { value: "me", caption: "Me" }, + { value: "all", caption: "All" } + ] + } + } + } + }, plugin); + } + + var drawn = false; + function draw() { + if (drawn) return; + drawn = true; + + // Insert HTML + var markup = require("text!./plugin.html"); + ui.insertHtml(document.body, markup, plugin); + + // Insert CSS + ui.insertCss(require("text!./style.css"), options.staticPrefix, plugin); + + emit("draw"); + } + + /***** Methods *****/ + + function show() { + draw(); + + var div = document.querySelector(".helloworld"); + div.style.display = "block"; + div.innerHTML = settings.get("user/my-plugin/@second"); + + emit("show"); + showing = true; + } + + function hide() { + if (!drawn) return; + + document.querySelector(".helloworld").style.display = "none"; + + emit("hide"); + showing = false; + } + + /***** Lifecycle *****/ + + plugin.on("load", function() { + load(); + }); + plugin.on("unload", function() { + drawn = false; + showing = false; + }); + + /***** Register and define API *****/ + + /** + * This is an example of an implementation of a plugin. + * @singleton + */ + plugin.freezePublicAPI({ + /** + * @property showing whether this plugin is being shown + */ + get showing(){ return showing; }, + + _events: [ + /** + * @event show The plugin is shown + */ + "show", + + /** + * @event hide The plugin is hidden + */ + "hide" + ], + + /** + * Show the plugin + */ + show: show, + + /** + * Hide the plugin + */ + hide: hide, + }); + + register(null, { + "myplugin": plugin + }); + } +}); \ No newline at end of file diff --git a/plugins/c9.ide.plugins/mock/c9.ide.example3/plugin_test.js b/plugins/c9.ide.plugins/mock/c9.ide.example3/plugin_test.js new file mode 100644 index 00000000..3ae2262d --- /dev/null +++ b/plugins/c9.ide.plugins/mock/c9.ide.example3/plugin_test.js @@ -0,0 +1,41 @@ +"use client"; +"use mocha"; + +define(function(require, exports, module) { + main.consumes = ["plugin.test", "myplugin"]; + main.provides = []; + return main; + + function main(options, imports, register) { + var test = imports["plugin.test"]; + var myplugin = imports.myplugin; + + var describe = test.describe; + var it = test.it; + var before = test.before; + var after = test.after; + var beforeEach = test.beforeEach; + var afterEach = test.afterEach; + var assert = test.assert; + var expect = test.expect; + + /***** Initialization *****/ + + describe(myplugin.name, function(){ + this.timeout(2000); + + it("shows a helloworld div", function() { + myplugin.show(); + expect(document.querySelector(".helloworld")).to.ok; + expect(document.querySelector(".helloworld").innerText).to.equal("all"); + }); + + it("hides the div", function() { + myplugin.hide(); + expect(document.querySelector(".helloworld").offsetHeight).to.not.ok; + }); + }); + + register(null, {}); + } +}); \ No newline at end of file diff --git a/plugins/c9.ide.plugins/market.js b/plugins/c9.ide.plugins/packages.js similarity index 63% rename from plugins/c9.ide.plugins/market.js rename to plugins/c9.ide.plugins/packages.js index 592f9bc6..4b466aef 100644 --- a/plugins/c9.ide.plugins/market.js +++ b/plugins/c9.ide.plugins/packages.js @@ -3,7 +3,7 @@ define(function(require, exports, module) { "Editor", "editors", "ui", "commands", "menus", "layout", "tabManager", "util", "settings", "api", "c9" ]; - main.provides = ["plugin.market"]; + main.provides = ["plugin.packages"]; return main; function main(options, imports, register) { @@ -24,17 +24,17 @@ define(function(require, exports, module) { var extensions = []; var packages = {}; - var handle = editors.register("plugin.market", "Market Place", - MarketPlace, extensions); + var handle = editors.register("plugin.packages", "Package Browser", + PackageBrowser, extensions); var emit = handle.getEmitter(); emit.setMaxListeners(1000); - var HASSDK = c9.location.indexOf("sdk=1") > -1; + var HASSDK = c9.location.indexOf("sdk=0") === -1; - function focusOpenMarket(){ + function focusOpenPackages(){ var pages = tabs.getTabs(); for (var i = 0, tab = pages[i]; tab; tab = pages[i++]) { - if (tab.editorType == "plugin.market") { + if (tab.editorType == "plugin.packages") { tabs.focusTab(tab); return true; } @@ -49,29 +49,30 @@ define(function(require, exports, module) { }); commands.addCommand({ - name: "openmarketplace", - hint: "open the market place", + name: "openpackagebrowser", + hint: "open the package browser", group: "General", // bindKey: { mac: "Command-,", win: "Ctrl-," }, exec: function () { var tab = tabs.focussedTab; - if (tab && tab.editor.type == "plugin.market") { + if (tab && tab.editor.type == "plugin.packages") { tab.close(); return; } - if (focusOpenMarket()) + if (focusOpenPackages()) return; tabs.open({ - editorType: "plugin.market", + editorType: "plugin.packages", active: true }, function(){}); } }, handle); - menus.addItemByPath("Cloud9/Plugin Store", new ui.item({ - command: "openmarketplace" - }), 301, handle); + menus.addItemByPath("Cloud9/~", new ui.divider(), 1000, handle); + menus.addItemByPath("Cloud9/Package Browser", new ui.item({ + command: "openpackagebrowser" + }), 1100, handle); }); /***** Methods *****/ @@ -123,10 +124,10 @@ define(function(require, exports, module) { /***** Editor *****/ - function MarketPlace(){ + function PackageBrowser(){ var plugin = new Editor("Ajax.org", main.consumes, extensions); //var emit = plugin.getEmitter(); - var tab; + var tab, iframe; plugin.on("resize", function(e) { emit("resize", e); @@ -136,33 +137,15 @@ define(function(require, exports, module) { tab = e.tab; var htmlNode = e.htmlNode; - api.packages.get("", function(err, list){ - if (c9.standalone) { - err = null; - list = [{ name: "example", apikey:"0000000000000000000000000000=", packagePath: "plugins/c9.example/example" }]; - } - - if (err) return; - - var sHtml = ""; - list.forEach(function(plugin){ // @todo use react instead in an iframe - packages[plugin.name] = plugin; - - sHtml += "
" - + "" + plugin.name + " | " - + "Install In Workspace | " - + "Install To User" - + "
"; - }); - - htmlNode.innerHTML = sHtml; - htmlNode.addEventListener("click", function(e){ - if (e.target.tagName == "A") { - installPlugin(e.target.getAttribute("plugin-name"), - e.target.getAttribute("target"), function(){}); - } - }); - }); + htmlNode.style.paddingTop = 0; + + iframe = htmlNode.appendChild(document.createElement("iframe")); + iframe.style.width = "100%"; + iframe.style.height = "100%"; + iframe.style.border = 0; + iframe.style.backgroundColor = "#fbfbfb"; + + iframe.src = location.origin.replace("ide.", "") + "/profile/packages?nobar=1&pid=" + c9.projectId; }); plugin.on("getState", function(e) { @@ -174,12 +157,11 @@ define(function(require, exports, module) { plugin.on("documentLoad", function(e) { var doc = e.doc; - doc.title = "Plugin Store"; + doc.title = "Package Browser"; function setTheme(){ - // var bg = ui.getStyleRule(".bar-preferences .container .header", "backgroundColor") || "#F0F0F0"; - var bg = "#FFF"; - doc.tab.backgroundColor = bg; //"#2d2d2d"; + var bg = "#fbfbfb"; + doc.tab.backgroundColor = bg; if (util.shadeColor(bg, 1).isLight) doc.tab.classList.remove("dark"); @@ -192,14 +174,7 @@ define(function(require, exports, module) { }); plugin.on("documentActivate", function(e) { - e.doc.tab.on("unload", function(){ - if (parent.parentNode == tab) - tab.removeChild(parent); - }); - tab.appendChild(parent); - - emit("show"); }); /***** Register and define API *****/ @@ -212,13 +187,13 @@ define(function(require, exports, module) { }); - plugin.load(null, "plugin.market"); + plugin.load(null, "plugin.packages"); return plugin; } register(null, { - "plugin.market": handle + "plugin.packages": handle }); } }); \ No newline at end of file diff --git a/plugins/c9.ide.plugins/templates/plugin.bundle/README.md b/plugins/c9.ide.plugins/templates/plugin.bundle/README.md new file mode 100644 index 00000000..143f51ff --- /dev/null +++ b/plugins/c9.ide.plugins/templates/plugin.bundle/README.md @@ -0,0 +1 @@ +This is the Cloud9 bundle example \ No newline at end of file diff --git a/plugins/c9.ide.plugins/templates/plugin.bundle/package.json b/plugins/c9.ide.plugins/templates/plugin.bundle/package.json new file mode 100644 index 00000000..dd39f32a --- /dev/null +++ b/plugins/c9.ide.plugins/templates/plugin.bundle/package.json @@ -0,0 +1,21 @@ +{ + "name": "", + "description": "", + "version": "0.0.1", + "author": "", + "contributors": [ + { + "name": "", + "email": "" + } + ], + "repository": { + "type": "git", + "url": "" + }, + "plugins": {}, + "categories": [ + "misc" + ], + "licenses": [] +} \ No newline at end of file diff --git a/plugins/c9.ide.plugins/templates/plugin.default/README.md b/plugins/c9.ide.plugins/templates/plugin.default/README.md index 6d7f5b94..600c4e28 100644 --- a/plugins/c9.ide.plugins/templates/plugin.default/README.md +++ b/plugins/c9.ide.plugins/templates/plugin.default/README.md @@ -1,3 +1 @@ -# c9.ide.example - This is the Cloud9 default plugin example \ No newline at end of file diff --git a/plugins/c9.ide.plugins/templates/plugin.default/package.json b/plugins/c9.ide.plugins/templates/plugin.default/package.json index 174c0262..3364c37a 100644 --- a/plugins/c9.ide.plugins/templates/plugin.default/package.json +++ b/plugins/c9.ide.plugins/templates/plugin.default/package.json @@ -1,5 +1,5 @@ { - "name": "c9.ide.default", + "name": "", "description": "", "version": "0.0.1", "author": "", diff --git a/plugins/c9.ide.plugins/templates/plugin.installer/README.md b/plugins/c9.ide.plugins/templates/plugin.installer/README.md new file mode 100644 index 00000000..9de9f3a9 --- /dev/null +++ b/plugins/c9.ide.plugins/templates/plugin.installer/README.md @@ -0,0 +1 @@ +This is the Cloud9 installer plugin example \ No newline at end of file diff --git a/plugins/c9.ide.plugins/templates/plugin.installer/install.js b/plugins/c9.ide.plugins/templates/plugin.installer/install.js new file mode 100644 index 00000000..f87bdec5 --- /dev/null +++ b/plugins/c9.ide.plugins/templates/plugin.installer/install.js @@ -0,0 +1,57 @@ +define(function(require, exports, module) { + +module.exports = function(session, options){ + // Dependencies for the collaboration features of Cloud9 + + session.install({ + "name": "SQLite", + "description": "SQLite Database and NPM module", + "cwd": "~/.c9", + "optional": true + }, [ + { + "npm": ["sqlite3@3.0.5"] + }, + { + "tar.gz": { + "url": "https://raw.githubusercontent.com/c9/install/master/packages/sqlite3/linux/sqlite3.tar.gz", + "target": "~/.c9/lib/sqlite3", + "dir": "sqlite3" + } + }, + { + "symlink": { + "source": "~/.c9/lib/sqlite3/sqlite3", + "target": "~/.c9/bin/sqlite3" + } + } + ]); + + session.install({ + "name": "Sequalize", + "description": "Sequalize NPM module", + "cwd": "~/.c9", + "optional": true + }, { + "npm": ["sequelize@2.0.0-beta.0"] + }); + + session.install({ + "name": "Collab Server", + "description": "A small Node.js collaboration server", + "cwd": "~/.c9", + "optional": true + }, { + "tar.gz": { + "url": "https://raw.githubusercontent.com/c9/install/master/packages/extend/c9-vfs-extend.tar.gz", + "target": "~/.c9" + } + }); + + // Show the installation screen + session.start(); +}; + +module.exports.version = 1; + +}); \ No newline at end of file diff --git a/plugins/c9.ide.plugins/templates/plugin.installer/package.json b/plugins/c9.ide.plugins/templates/plugin.installer/package.json new file mode 100644 index 00000000..9013bf77 --- /dev/null +++ b/plugins/c9.ide.plugins/templates/plugin.installer/package.json @@ -0,0 +1,22 @@ +{ + "name": "", + "description": "", + "version": "0.0.1", + "author": "", + "contributors": [ + { + "name": "", + "email": "" + } + ], + "repository": { + "type": "git", + "url": "" + }, + "plugins": {}, + "installer": "install.js", + "categories": [ + "misc" + ], + "licenses": [] +} \ No newline at end of file diff --git a/plugins/c9.ide.plugins/templates/plugin.simple/README.md b/plugins/c9.ide.plugins/templates/plugin.simple/README.md index 566c1196..8ac7218b 100644 --- a/plugins/c9.ide.plugins/templates/plugin.simple/README.md +++ b/plugins/c9.ide.plugins/templates/plugin.simple/README.md @@ -1,3 +1 @@ -# c9.ide.simple - This is the Cloud9 simple plugin example \ No newline at end of file diff --git a/plugins/c9.ide.plugins/templates/plugin.simple/package.json b/plugins/c9.ide.plugins/templates/plugin.simple/package.json index f782d88e..3364c37a 100644 --- a/plugins/c9.ide.plugins/templates/plugin.simple/package.json +++ b/plugins/c9.ide.plugins/templates/plugin.simple/package.json @@ -1,5 +1,5 @@ { - "name": "c9.ide.simple", + "name": "", "description": "", "version": "0.0.1", "author": "", diff --git a/plugins/c9.ide.terminal/terminal.js b/plugins/c9.ide.terminal/terminal.js index dcc8e9a0..05954eb6 100644 --- a/plugins/c9.ide.terminal/terminal.js +++ b/plugins/c9.ide.terminal/terminal.js @@ -750,6 +750,9 @@ define(function(require, exports, module) { session.__defineGetter__("tab", function(){ return doc.tab }); session.__defineGetter__("doc", function(){ return doc }); + session.__defineGetter__("defaultEditor", function(){ + return settings.getBool("user/terminal/@defaultEditor"); + }); session.attach = function(){ if (session.aceSession && aceterm) { diff --git a/plugins/c9.ide.terminal/tmux_connection.js b/plugins/c9.ide.terminal/tmux_connection.js index fafcfadb..1576ee80 100644 --- a/plugins/c9.ide.terminal/tmux_connection.js +++ b/plugins/c9.ide.terminal/tmux_connection.js @@ -157,6 +157,7 @@ module.exports = function(c9, proc, installPath, shell) { options.output = false; options.terminal = true; options.detachOthers = !session.hasConnected; + options.defaultEditor = session.defaultEditor; } // Connect to backend and start tmux session diff --git a/plugins/c9.ide.watcher/gui.js b/plugins/c9.ide.watcher/gui.js index a4489de9..6bf4147a 100644 --- a/plugins/c9.ide.watcher/gui.js +++ b/plugins/c9.ide.watcher/gui.js @@ -309,11 +309,8 @@ define(function(require, exports, module) { doc.meta.$mergeRoot = data; // If the value on disk is the same as in the document, set the bookmark - if (mergedValue == data) { - doc.undoManager.once("change", function(){ - doc.undoManager.bookmark(); - }); - } + if (mergedValue == data) + doc.undoManager.bookmark(); return true; } diff --git a/plugins/c9.nodeapi/events.js b/plugins/c9.nodeapi/events.js index 5bf4e755..027cace5 100644 --- a/plugins/c9.nodeapi/events.js +++ b/plugins/c9.nodeapi/events.js @@ -146,7 +146,7 @@ EventEmitter.prototype.addListener = function(type, listener, plugin) { if (m && m > 0 && eventList.length > m) { eventList.warned = true; - console.error('(node) warning: possible EventEmitter memory ' + console.error('warning: possible EventEmitter memory ' + 'leak detected. " + eventList.length + " listeners of type "' + type + '" added. ' + 'Use emitter.setMaxListeners() to increase limit.' ); diff --git a/plugins/c9.vfs.standalone/standalone.js b/plugins/c9.vfs.standalone/standalone.js index 137f8680..5779888e 100644 --- a/plugins/c9.vfs.standalone/standalone.js +++ b/plugins/c9.vfs.standalone/standalone.js @@ -156,6 +156,14 @@ function plugin(options, imports, register) { res.end("define(function(require, exports, module) { return '" + options.workspaceDir + "'; });"); }); + api.get("/vfs-home", function(req, res, next) { + if (!options.options.testing) + return next(); + + res.writeHead(200, {"Content-Type": "application/javascript"}); + res.end("define(function(require, exports, module) { return '" + + process.env.HOME + "'; });"); + }); api.get("/update", function(req, res, next) { res.writeHead(200, { diff --git a/plugins/c9.vfs.standalone/www/test.js b/plugins/c9.vfs.standalone/www/test.js index cce8b53b..2b3ea410 100644 --- a/plugins/c9.vfs.standalone/www/test.js +++ b/plugins/c9.vfs.standalone/www/test.js @@ -195,6 +195,7 @@ require([ })(), log: {}, http: {}, + ui: {}, api: { stats: { post: function(type, message, cb) { @@ -414,8 +415,13 @@ require([ "metrics": { getLastPing: function() { throw Error("Not implemented"); }, getLastest: function() { throw Error("Not implemented"); }, + log: function() {}, + increment: function() {} + }, + error_handler: { + log: function() {}, + reportError: function(){} }, - error_handler: {reportError: function(){}}, proc: { execFile: function() {}, spawn: function() {} diff --git a/settings/standalone.js b/settings/standalone.js index c65482db..4fd9ac74 100644 --- a/settings/standalone.js +++ b/settings/standalone.js @@ -28,6 +28,7 @@ module.exports = function(manifest, installPath) { var config = { standalone: true, + startBridge: true, manifest: manifest, workspaceDir: workspaceDir, projectName: path.basename(workspaceDir),