diff --git a/configs/cli.js b/configs/cli.js index 80a8d3f5..e3fd3411 100644 --- a/configs/cli.js +++ b/configs/cli.js @@ -80,6 +80,10 @@ return [ packagePath: "./c9.cli.open/open", platform: process.platform }, + { + packagePath: "./c9.cli.exec/exec", + platform: process.platform + }, { packagePath: "./c9.cli.open/restart", platform: process.platform diff --git a/configs/client-default.js b/configs/client-default.js index 0cc65505..9bc8bc4a 100644 --- a/configs/client-default.js +++ b/configs/client-default.js @@ -317,7 +317,10 @@ module.exports = function(options) { "plugins/c9.ide.language/tooltip", "plugins/c9.ide.language/jumptodef", "plugins/c9.ide.language/worker_util_helper", - "plugins/c9.ide.language.generic/generic", + { + packagePath: "plugins/c9.ide.language.generic/generic", + mode_completer: options.ssh, + }, "plugins/c9.ide.language.css/css", "plugins/c9.ide.language.html/html", "plugins/c9.ide.language.javascript/javascript", @@ -694,14 +697,15 @@ module.exports = function(options) { no_newsletter: options.user.no_newsletter, subscription_on_signup: options.user.subscription_on_signup, premium: options.user.premium, - region: options.user.region + region: options.user.region, }, project: { id: options.project.id, name: options.project.name, contents: options.project.contents, descr: options.project.descr, - remote: options.project.remote + remote: options.project.remote, + premium: options.project.premium, } }, { @@ -719,12 +723,6 @@ module.exports = function(options) { packagePath: "plugins/c9.cli.bridge/bridge_commands", basePath: workspaceDir }, - { - packagePath: "plugins/c9.ide.help.support/support", - baseurl: options.ideBaseUrl, - userSnapApiKey: options.support.userSnapApiKey, - screenshotSupport: true - }, { packagePath: "plugins/c9.ide.help/help", staticPrefix: staticPrefix + "/plugins/c9.ide.help" @@ -811,13 +809,28 @@ module.exports = function(options) { "plugins/c9.ide.test.mocha/mocha", + // git integration v2 + // { + // packagePath: "plugins/c9.ide.scm/scm.commit", + // staticPrefix: staticPrefix + "/plugins/c9.ide.scm" + // }, + // "plugins/c9.ide.scm/scm", + // "plugins/c9.ide.scm/scm.branches", + // "plugins/c9.ide.scm/dialog.localchanges", + // "plugins/c9.ide.scm/scm.log", + // "plugins/c9.ide.scm/git", + // "plugins/c9.ide.scm/diff.split", + // "plugins/c9.ide.scm/diff.unified", + + // // git integration v1 + "plugins/c9.ide.scm/v1/scm", + "plugins/c9.ide.scm/v1/scmpanel", + "plugins/c9.ide.scm/v1/detail", + "plugins/c9.ide.scm/v1/log", + "plugins/c9.ide.scm/v1/git", + "plugins/c9.ide.scm/v1/editor", + // git integration - "plugins/c9.ide.scm/scm", - "plugins/c9.ide.scm/scmpanel", - "plugins/c9.ide.scm/detail", - "plugins/c9.ide.scm/log", - "plugins/c9.ide.scm/git", - "plugins/c9.ide.scm/editor", "plugins/c9.ide.scm/mergetool" ]; diff --git a/docs/CODING_STANDARDS.md b/docs/CODING_STANDARDS.md index 6a6eea2d..8318a955 100644 --- a/docs/CODING_STANDARDS.md +++ b/docs/CODING_STANDARDS.md @@ -695,6 +695,31 @@ API Documentation All classes and public API should be documented using [JSDuck annotations](https://github.com/senchalabs/jsduck). +Commit messages +--------------- + +We try to adhere to https://github.com/blog/926-shiny-new-commit-styles and to a lesser extent http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html. +Don't write `I fixed a bug` or `Fixed bug`, or even `Added a cool fix for bug`. Just write `Fix bug in wrop wraffles` or `Add feature flip floppers`, present tense. + +Branch Naming +------------- + +We follow the uni-repo approach so our source code is in one place. To work around some of the issues - for example looking at all PRs affecting a certain service - we prefix branches with the name of the service(s) the branch affects. + +PR branch names, e.g. + + “api-”, “ide-”, “multi-ide-vfs-sapi-” + +Checking for branch naming consistency is part of the review process and the teams responsibility. + + Use “all-” in case of doubt. E.g., https://github.com/c9/newclient/pull/12962/files affects redis schema code. + +Generally, releasing changes affecting several services is a smell so this can help you identify possible issues. + +You can now look for all PRs which made it in like so (api in this case): + + git log --oneline --first-parent SHA..origin/master | grep -v bump | grep api- + Other Resources =============== diff --git a/node_modules/ace/lib/ace/edit_session/folding.js b/node_modules/ace/lib/ace/edit_session/folding.js index fbab03fa..4e67fe45 100644 --- a/node_modules/ace/lib/ace/edit_session/folding.js +++ b/node_modules/ace/lib/ace/edit_session/folding.js @@ -772,7 +772,7 @@ function Folding() { this.removeFold(fold); else this.expandFold(fold); - return; + return fold; } var range = this.getFoldWidgetRange(row, true); @@ -781,7 +781,7 @@ function Folding() { fold = this.getFoldAt(range.start.row, range.start.column, 1); if (fold && range.isEqual(fold.range)) { this.removeFold(fold); - return; + return fold; } } diff --git a/node_modules/ace/lib/ace/ext/whitespace.js b/node_modules/ace/lib/ace/ext/whitespace.js index 7f39b092..04074988 100644 --- a/node_modules/ace/lib/ace/ext/whitespace.js +++ b/node_modules/ace/lib/ace/ext/whitespace.js @@ -127,10 +127,20 @@ exports.trimTrailingSpace = function(session, options) { var lines = doc.getAllLines(); var min = options && options.trimEmpty ? -1 : 0; - var cursors = session.selection.rangeCount - ? session.selection.ranges.map(function(x) { return x.cursor; }) - : [session.selection.getCursor()]; - var ci = options && options.keepCursorPosition ? 0 : -1; + var cursors = [], ci = -1; + if (options && options.keepCursorPosition) { + if (session.selection.rangeCount) { + session.selection.rangeList.ranges.forEach(function(x, i, ranges) { + var next = ranges[i + 1]; + if (next && next.cursor.row == x.cursor.row) + return; + cursors.push(x.cursor); + }); + } else { + cursors.push(session.selection.getCursor()); + } + ci = 0; + } var cursorRow = cursors[ci] && cursors[ci].row; for (var i = 0, l=lines.length; i < l; i++) { @@ -138,8 +148,8 @@ exports.trimTrailingSpace = function(session, options) { var index = line.search(/\s+$/); if (i == cursorRow) { - if (index < cursors[ci].column) - index = min; + if (index < cursors[ci].column && index > min) + index = cursors[ci].column; ci++; cursorRow = cursors[ci] ? cursors[ci].row : -1; } diff --git a/node_modules/ace/lib/ace/ext/whitespace_test.js b/node_modules/ace/lib/ace/ext/whitespace_test.js index 419ae74f..422d346c 100644 --- a/node_modules/ace/lib/ace/ext/whitespace_test.js +++ b/node_modules/ace/lib/ace/ext/whitespace_test.js @@ -1,13 +1,14 @@ if (typeof process !== "undefined") { require("amd-loader"); - require("../test/mockdom"); } define(function(require, exports, module) { "use strict"; +require("../multi_select"); var assert = require("assert"); var EditSession = require("../edit_session").EditSession; +var UndoManager = require("../undomanager").UndoManager; var whitespace = require("./whitespace"); // Execution ORDER: test.setUpSuite, setUp, testFn, tearDown, test.tearDownSuite @@ -111,8 +112,90 @@ module.exports = { assert.equal(indent.length, 1); next(); - } + }, + "test trimTrailingSpace": function(next) { + var session = new EditSession([ + "a", + "\t b \t", + " ", + "\t", + "\t\tx\t\t", + " ", + " " + ]); + session.setUndoManager(new UndoManager()); + + function testOne(value, options) { + console.log(JSON.stringify(session.getValue())) + + whitespace.trimTrailingSpace(session, options); + assert.equal(value, session.getValue()); + session.markUndoGroup(); + session.getUndoManager().undo(); + } + + testOne("a\n\t b\n \n\t\n\t\tx\n \n ") + + testOne("a\n\t b\n\n\n\t\tx\n\n", { + trimEmpty: true + }); + + session.selection.fromJSON([{ + start: {row:2,column:3}, + end: {row:4,column:4} + }]); + testOne("a\n\t b\n\n\n\t\tx\t\n\n", { + keepCursorPosition: true, + trimEmpty: true + }); + + session.selection.fromJSON([{ + start: {row:2,column:3}, + end: {row:4,column:4}, + isBackwards: true + }]); + testOne("a\n\t b\n \n\n\t\tx\n\n", { + keepCursorPosition: true, + trimEmpty: true + }); + + session.selection.$initRangeList(); + session.selection.fromJSON([{ + start: {row:2, column:3}, + end: {row:2,column:3} + }, { + start: {row:1, column:1}, + end: {row:1, column:1} + }, { + start: {row:2,column:2}, + end: {row:2,column:2} + }, { + start: {row:0,column:5}, + end: {row:0,column:5}, + isBackwards:false + }, { + start: {row:6,column:1}, + end: {row:6,column:1}, + isBackwards:false + }]); + testOne("a\n\t b\n \n\n\t\tx\n\n ", { + trimEmpty: true, + keepCursorPosition: true + }); + + session.setValue("some text"); + session.selection.fromJSON([{ + start: {row:0,column:4}, + end: {row:0,column:4} + }]); + testOne("some text", { + keepCursorPosition: true, + trimEmpty: true + }); + + next(); + }, }; }); diff --git a/node_modules/ace/lib/ace/line_widgets.js b/node_modules/ace/lib/ace/line_widgets.js index 6eaffc88..83b0af94 100644 --- a/node_modules/ace/lib/ace/line_widgets.js +++ b/node_modules/ace/lib/ace/line_widgets.js @@ -214,7 +214,7 @@ function LineWidgets(session) { if (!w.coverGutter) { w.el.style.zIndex = 3; } - if (!w.pixelHeight) { + if (w.pixelHeight == null) { w.pixelHeight = w.el.offsetHeight; } if (w.rowCount == null) { diff --git a/node_modules/ace/lib/ace/mode/diff.js b/node_modules/ace/lib/ace/mode/diff.js index 72671a27..f3c46a1b 100644 --- a/node_modules/ace/lib/ace/mode/diff.js +++ b/node_modules/ace/lib/ace/mode/diff.js @@ -38,7 +38,7 @@ var FoldMode = require("./folding/diff").FoldMode; var Mode = function() { this.HighlightRules = HighlightRules; - this.foldingRules = new FoldMode(["diff", "index", "\\+{3}", "@@|\\*{5}"], "i"); + this.foldingRules = new FoldMode(["diff", "@@|\\*{5}"], "i"); }; oop.inherits(Mode, TextMode); diff --git a/node_modules/ace/lib/ace/mode/folding/diff.js b/node_modules/ace/lib/ace/mode/folding/diff.js index 23c0e2d0..54185ad1 100644 --- a/node_modules/ace/lib/ace/mode/folding/diff.js +++ b/node_modules/ace/lib/ace/mode/folding/diff.js @@ -61,7 +61,7 @@ oop.inherits(FoldMode, BaseFoldMode); } if (row == start.row + 1) return; - return Range.fromPoints(start, {row: row - 1, column: line.length}); + return new Range(start.row, start.column, row - 1, line.length); }; }).call(FoldMode.prototype); diff --git a/node_modules/ace/lib/ace/worker/worker.js b/node_modules/ace/lib/ace/worker/worker.js index 856a6ea9..eb5bb876 100644 --- a/node_modules/ace/lib/ace/worker/worker.js +++ b/node_modules/ace/lib/ace/worker/worker.js @@ -200,7 +200,7 @@ window.onmessage = function(e) { sender._signal(msg.event, msg.data); } else if (msg.command) { - if (main[msg.command]) + if (main && main[msg.command]) main[msg.command].apply(main, msg.args); else if (window[msg.command]) window[msg.command].apply(window, msg.args); diff --git a/node_modules/ace/lib/ace/worker/worker_client.js b/node_modules/ace/lib/ace/worker/worker_client.js index 42c08f43..104f198c 100644 --- a/node_modules/ace/lib/ace/worker/worker_client.js +++ b/node_modules/ace/lib/ace/worker/worker_client.js @@ -36,7 +36,7 @@ var net = require("../lib/net"); var EventEmitter = require("../lib/event_emitter").EventEmitter; var config = require("../config"); -var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) { +var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl, importScripts) { this.$sendDeltaQueue = this.$sendDeltaQueue.bind(this); this.changeListener = this.changeListener.bind(this); this.onMessage = this.onMessage.bind(this); @@ -75,6 +75,9 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) { throw e; } } + if (importScripts) { + this.send("importScripts", importScripts); + } this.$worker.postMessage({ init : true, tlns : tlns, @@ -94,7 +97,7 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) { this.onMessage = function(e) { var msg = e.data; - switch(msg.type) { + switch (msg.type) { case "event": this._signal(msg.name, {data: msg.data}); break; @@ -157,7 +160,7 @@ var WorkerClient = function(topLevelNamespaces, mod, classname, workerUrl) { }; this.attachToDocument = function(doc) { - if(this.$doc) + if (this.$doc) this.terminate(); this.$doc = doc; diff --git a/node_modules/ace_tree/lib/ace_tree/data_provider.js b/node_modules/ace_tree/lib/ace_tree/data_provider.js index 4709fe1c..9bc095e0 100644 --- a/node_modules/ace_tree/lib/ace_tree/data_provider.js +++ b/node_modules/ace_tree/lib/ace_tree/data_provider.js @@ -49,7 +49,7 @@ var DataProvider = function(root) { }; this.open = - this.expand = function(node, deep, silent, justLoaded) { + this.expand = function(node, deep, silent) { if (typeof deep != "number") deep = deep ? 100 : 0; if (!node) @@ -69,7 +69,7 @@ var DataProvider = function(root) { this.collapse(node, null, true); node.status = "loaded"; if (!err) - this.expand(node, null, false, true); + this.expand(node, null, false); }.bind(this)); this.setOpen(node, true); return; @@ -95,9 +95,6 @@ var DataProvider = function(root) { } } - if (justLoaded) - node.justLoaded = true; - this.rows = items.length; silent || this._signal("expand", node); }; @@ -417,7 +414,7 @@ DataProvider.variableHeightRowMixin = function() { var items = this.visibleItems; var top = 0, index = 0, l = items.length; while (index < l) { - var height = this.getItemHeight(items[index].height, index); + var height = this.getItemHeight(items[index], index); top += height; index++; if (top >= offset) { @@ -445,7 +442,7 @@ DataProvider.variableHeightRowMixin = function() { var items = this.visibleItems; var startH = 0, index = 0, l = items.length; while (index < l) { - var height = this.getItemHeight(items[index].height, index); + var height = this.getItemHeight(items[index], index); startH += height; index++; if (startH >= top) { diff --git a/node_modules/ace_tree/lib/ace_tree/edit.js b/node_modules/ace_tree/lib/ace_tree/edit.js index 4dcc71e4..a76f115f 100644 --- a/node_modules/ace_tree/lib/ace_tree/edit.js +++ b/node_modules/ace_tree/lib/ace_tree/edit.js @@ -157,11 +157,12 @@ var EditableTree = function(tree) { this.ace.commands.bindKeys({ "Esc": function(ace) { ace.treeEditor.endRename(true); - ace.treeEditor.tree.focus(); }, "Enter": function(ace) { ace.treeEditor.endRename(); - ace.treeEditor.tree.focus(); + }, + "ctrl-s|cmd-s": function(ace) { + ace.treeEditor.endRename(); }, "Tab": function(ace) { ace.treeEditor.editNext(1); @@ -249,17 +250,26 @@ var EditableTree = function(tree) { this._destroyEditor = function() { if (this.lastDomNode) { this.lastDomNode.style.color = ""; - this.lastDomNode + this.lastDomNode; } this.ace.off("blur", this._onBlur); this.tree.renderer.off("afterRender", this._onAfterRender); - this.ace.blur(); - this.ace.destroy(); - if (this.ace.wrapper.parentNode) - this.ace.wrapper.parentNode.removeChild(this.ace.wrapper); + var ace = this.ace; this.ace = null; + this.$lastAce = ace; + ace.renderer.freeze(); + setTimeout(function() { + // doing this after timeout to allow rename event focus something else + var wasFocused = ace.isFocused(); + ace.destroy(); + if (ace.wrapper.parentNode) + ace.wrapper.parentNode.removeChild(ace.wrapper); + if (wasFocused) + this.tree.focus(); + this.$lastAce = null; + }.bind(this)); }; this.findNextEditPoint = function(dir, node, col, keepColumn) { @@ -384,6 +394,7 @@ var EditableTree = function(tree) { this.tree._emit("rename", { node: node, value: val, + oldValue: this.origVal, column: this.column }); this.tree.provider._signal("change"); diff --git a/node_modules/ace_tree/lib/ace_tree/layer/cells.js b/node_modules/ace_tree/lib/ace_tree/layer/cells.js index 9a25c4f8..c0eeffeb 100644 --- a/node_modules/ace_tree/lib/ace_tree/layer/cells.js +++ b/node_modules/ace_tree/lib/ace_tree/layer/cells.js @@ -51,7 +51,7 @@ var Cells = function(parentEl) { this.$renderRow(html, datarow, vsize, hsize, row); } - if (firstRow === 0 && lastRow === 0) { + if (firstRow <= 0 && lastRow <= 0) { this.renderPlaceHolder(provider, html, config); } @@ -130,7 +130,7 @@ var Cells = function(parentEl) { provider.renderRow(row, html, config); } - if (firstRow === 0 && lastRow === 0) { + if (firstRow <= 0 && lastRow <= 0) { this.renderPlaceHolder(provider, html, config); } @@ -187,7 +187,7 @@ var Cells = function(parentEl) { } else if (provider.getEmptyMessage) { html.push( "
", - provider.getEmptyMessage(), + escapeHTML(provider.getEmptyMessage()), "
" ); } diff --git a/node_modules/ace_tree/lib/ace_tree/layer/heading.js b/node_modules/ace_tree/lib/ace_tree/layer/heading.js index 834155fd..60256532 100644 --- a/node_modules/ace_tree/lib/ace_tree/layer/heading.js +++ b/node_modules/ace_tree/lib/ace_tree/layer/heading.js @@ -68,6 +68,7 @@ function ColumnHeader(parentEl, renderer) { } col.pixelWidth = 0; }, this); + columns.fixedWidth = fixedWidth; columns.$fixedWidth = fixedWidth + "px"; columns.width = null; provider.columns = columns; diff --git a/node_modules/architect-build/build_support/mini_require.js b/node_modules/architect-build/build_support/mini_require.js index 004cde85..c04e9e77 100644 --- a/node_modules/architect-build/build_support/mini_require.js +++ b/node_modules/architect-build/build_support/mini_require.js @@ -427,7 +427,7 @@ var host = location.protocol + "//" + location.hostname + (location.port ? ":" + var loadScript = function(path, id, callback) { if (!config.useCache) return loadScriptWithTag(path, id, callback); - if (!/https?:/.test(path)) + if (!/^https?:/.test(path)) path = host + path; var cb = function(e, val, deps) { if (e) return processLoadQueue({ id: id, path: path }); @@ -498,10 +498,12 @@ function checkCache() { }).then(function(keys) { baseUrl = host + config.baseUrl; var val = keys.map(function(r) { - var url = r.url; - if (url.startsWith(baseUrl)) - url = url.slice(baseUrl.length); - return r.headers.get("etag") + " " + url; + var url = r.url; + if (url.startsWith(baseUrl)) + url = url.slice(baseUrl.length); + else if (/^\w+:/.test(url)) + return ""; + return r.headers.get("etag") + " " + url; }).join("\n") + "\n"; if (val.length == 1) { ideCachePromiss = null; @@ -510,7 +512,7 @@ function checkCache() { return new Promise(function(resolve) { var checked = 0; var buffer = ""; - var toDelete = [] + var toDelete = []; post("/static/__check__", val, function(t) { var e = t.slice(checked); checked = t.length; @@ -539,6 +541,22 @@ function checkCache() { return ideCachePromiss; } +require.clearCache = function(callback) { + ideCachePromiss = window.caches.open("ide").then(function(ideCache_) { + ideCache = ideCache_; + return ideCache.keys(); + }).then(function(keys) { + var toDelete = keys.map(function(i) { + ideCache.delete(i); + }); + Promise.all(toDelete).then(function() { + callback && callback(); + }, function(e) { + callback && callback(e); + }); + }); +} + function post(path, val, progress, cb) { var xhr = new window.XMLHttpRequest(); xhr.open("POST", path, true); diff --git a/node_modules/architect-build/npm_freeze.js b/node_modules/architect-build/npm_freeze.js new file mode 100644 index 00000000..eb3a1353 --- /dev/null +++ b/node_modules/architect-build/npm_freeze.js @@ -0,0 +1,69 @@ +var patchTemplate = ";(" +function() { + // automatically generated by architect-build + // bundle all dependencies in the main package + var Module = require("module"); + var path = require("path"); + var _resolveFilename_orig = Module._resolveFilename + var root = path.join(__dirname, "{root}"); + Module._resolveFilename = function(id, parent) { + if (parent && parent.paths && parent.paths[0] && parent.paths[0].indexOf(root) == 0) { + parent.paths = parent.paths.map(function(p) { + if (p.indexOf(root) != 0) return + p = p.slice(root.length); + return root + p.replace(/([\\\/]|^)node_modules([\\\/]|$)/g, "$1n_m$2"); + }).filter(Boolean); + } + return _resolveFilename_orig.call(Module, id, parent); + }; +} + ")();"; + +////////////////////////////////////////////////////////////////////////// + +var fs = require("fs"); +var copy = require("architect-build/copy"); +var pathLib = require("path"); + +function freeze(path, mode) { + path = copy.convertPath(path); + var pkgs = {}; + function freezePkg(path) { + if (!pkgs[path]) { + pkgs[path] = JSON.parse(fs.readFileSync(path + "/package.json")) + Object.keys(pkgs[path]).forEach(function(k) { + if (/Dependencies/i.test(k)) delete pkgs[path][k]; + }) + } + pkgs[path].dependencies = {}; + var deps = fs.existsSync(path + "/node_modules") ? fs.readdirSync(path + "/node_modules") : []; + deps.forEach(function(n) { + if (n == ".bin") return; + var depPath = path + "/node_modules/" + n; + freezePkg(depPath); + pkgs[path].dependencies[n] = pkgs[depPath].version; + }); + if (mode == "rename") { + if (deps.length) { + fs.renameSync(path + "/node_modules/", path + "/n_m") + delete pkgs[path].dependencies + if (pkgs[path].files) + pkgs[path].files.push("n_m") + } + } else { + pkgs[path].bundledDependencies = Object.keys(pkgs[path].dependencies); + } + fs.writeFileSync(path + "/package.json", JSON.stringify(pkgs[path], null, "\t"), "utf8"); + } + freezePkg(path); + var bin = pkgs[path].bin; + Object.keys(bin).forEach(function(key) { + var p = bin[key]; + var root = pathLib.relative(pathLib.dirname(p), "").replace(/[\\]/g, "/"); + var src = fs.readFileSync(path + "/" + p, "utf8"); + src = src.replace(/^(#.*|"use strict";?|\s)*/, function(a) { + return a.trim() + "\n\n" + patchTemplate.replace(/{root}/g, root) + "\n\n"; + }); + fs.writeFileSync(path + "/" + p, src, "utf8"); + }); +} + +module.exports = freeze; diff --git a/node_modules/c9/format-user-analytics.js b/node_modules/c9/format-user-analytics.js index d420e4d2..579975f1 100644 --- a/node_modules/c9/format-user-analytics.js +++ b/node_modules/c9/format-user-analytics.js @@ -23,7 +23,9 @@ define(function(require, exports, module) { name: user.fullname || user.name, pricingPlan: user.premium ? "Premium" : "Free", referredBy: user.referrer, - region: user.region + region: user.region, + usertype: user.usertype, + purpose: user.purpose, }; return traits; diff --git a/node_modules/c9/is-notfound.js b/node_modules/c9/is-notfound.js new file mode 100644 index 00000000..dcc8edba --- /dev/null +++ b/node_modules/c9/is-notfound.js @@ -0,0 +1,7 @@ +"use strict"; + + +module.exports = function isNotFound(err) { + if (err && err.code === 404) return true; + return false; +}; \ No newline at end of file diff --git a/node_modules/c9/rest_client.js b/node_modules/c9/rest_client.js index 8f42b1d0..0aec2fff 100644 --- a/node_modules/c9/rest_client.js +++ b/node_modules/c9/rest_client.js @@ -36,7 +36,7 @@ function RestClient(host, port, config) { var headers = _.extend({ "Accept": "application/json", "Content-Type": "application/json", - "Content-Length": payload.length + "Content-Length": Buffer.byteLength(payload) }, config.headers || {}); var options = { @@ -44,7 +44,11 @@ function RestClient(host, port, config) { port: port, path: path, method: method, - headers: headers + headers: headers, + timeout: config.timeout || 60 * 1000, + pool: config.pool || { + maxSockets: 100000, + }, }; if (config.username) options.auth = config.username + ":" + config.password; diff --git a/node_modules/c9/rest_client_test.js b/node_modules/c9/rest_client_test.js new file mode 100644 index 00000000..a0f07779 --- /dev/null +++ b/node_modules/c9/rest_client_test.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +/*global describe it before after beforeEach afterEach */ +"use strict"; + +"use server"; +"use mocha"; + +require("c9/inline-mocha")(module); +require("amd-loader"); + +var assert = require("assert"); +var http = require("http"); +var findFreePort = require("netutil").findFreePort; +var RestClient = require("./rest_client"); + +describe(__filename, function() { + + var port; + var MIN_API_PORT = 18500; + var MAX_API_PORT = MIN_API_PORT + 1000; + + beforeEach(function(next) { + findFreePort(MIN_API_PORT, MAX_API_PORT, 'localhost', function(err, _port) { + port = _port; + next(err); + }); + }); + + it("should send correct content length", function(next) { + var server = http.createServer(function(req, res) { + var body = ""; + req.on("data", function(d) { + body += d; + }); + + req.on("end", function() { + JSON.parse(body); + res.end("OK"); + }); + + }); + server.listen(port, function() { + var client = new RestClient("localhost", port, {}); + + // send body with "strange" unicode character + var body = {"cloneFromScm":"https://github.com/saasbook/ruby­-calisthenics"}; + client.request("POST", "/", body, function(err, res) { + assert(!err, err); + assert.equal(res, "OK"); + server.close(next); + }); + }); + }); +}); \ No newline at end of file diff --git a/node_modules/vfs-local/localfs.js b/node_modules/vfs-local/localfs.js index 0eba62a4..6885fefa 100644 --- a/node_modules/vfs-local/localfs.js +++ b/node_modules/vfs-local/localfs.js @@ -200,7 +200,9 @@ module.exports = function setup(fsOptions) { // Extending the API extend: extend, unextend: unextend, - use: use + use: use, + + workspaceDir: fsOptions.projectDir }); function wrapDomain(api) { @@ -1030,7 +1032,16 @@ module.exports = function setup(fsOptions) { if (!exists || options.overwrite || isSamePath) { // Rename the file fs.rename(frompath, topath, function (err) { - if (err) return callback(err); + if (err) { + if (err.code == 'ENOENT' && options.mkdirP != false) { + options.mkdirP = false; + return mkdirP(dir, {}, function(err) { + if (err) return callback(err); + rename(path, options, callback); + }); + } + return callback(err); + } // Rename metadata if (options.metadata !== false) { @@ -1551,7 +1562,7 @@ module.exports = function setup(fsOptions) { // todo add resize event proc.emit("data", {rows: rows, cols: cols}); - if (!tmuxWarned) { + if (!tmuxWarned && !isWin) { if (/v0\.([123456789]\..*|10\.(0|1|2[0-7]))/.test(process.version)) { proc.emit("data", { message: "Wrong Node.js version: " + process.version, @@ -1827,9 +1838,11 @@ module.exports = function setup(fsOptions) { } else if (options.command) { args.push(BASH + " -l -c '" - + options.command.replace(/'/g, "'\\''") - + '; printf "\\e[01;30m\\n\\nProcess exited with code: $?\\e[0m\\n"' - + "; sleep 0.1;'"); + + ( + 'trap \'printf "\\e[01;30m\\n\\nProcess exited with code: $?\\e[0m\\n"\' EXIT\n' + + options.command + ).replace(/'/g, "'\\''") + + "'"); } args.push( @@ -1995,15 +2008,20 @@ module.exports = function setup(fsOptions) { }; this.destroy = function(){ - pty.destroy.apply(pty, arguments); + return pty.destroy.apply(pty, arguments); }; this.end = function(){ - pty.end.apply(pty, arguments); + return pty.end.apply(pty, arguments); }; this.write = function() { - pty.write.apply(pty, arguments); + return pty.write.apply(pty, arguments); + }; + + this.resize = function() { + if (!exited) + return pty.resize.apply(pty, arguments); }; // this.acknowledgeWrite = function(callback) { diff --git a/node_modules/vfs-socket/worker.js b/node_modules/vfs-socket/worker.js index fbf197a3..ed7a282b 100644 --- a/node_modules/vfs-socket/worker.js +++ b/node_modules/vfs-socket/worker.js @@ -437,6 +437,8 @@ function Worker(vfs) { var keys = Object.keys(meta || {}); for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; + if (meta[key] == undefined) + continue; switch (key) { case "stream": token.stream = storeStream(meta.stream); break; case "process": token.process = storeProcess(meta.process); break; diff --git a/package.json b/package.json index 013a3419..0d6efb58 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "c9", "description": "New Cloud9 Client", - "version": "3.1.1776", + "version": "3.1.2074", "author": "Ajax.org B.V. ", "private": true, "main": "bin/c9", @@ -26,7 +26,7 @@ "mkdirp": "~0.3.5", "msgpack-js": "~0.1.1", "msgpack-js-browser": "~0.1.4", - "nak": "git://github.com/cloud9ide/nak.git#719f2188ec1d8e4ce553286e80737d7e6434d777", + "nak": "git://github.com/cloud9ide/nak.git#6deef931594", "netutil": "~0.0.2", "optimist": "~0.6.0", "qs": "0.6.6", @@ -55,24 +55,24 @@ "c9" ], "c9plugins": { - "c9.ide.language": "#b392f03a6c", + "c9.ide.language": "#e058ff301f", "c9.ide.language.css": "#be07d72209", - "c9.ide.language.generic": "#92210f5a48", + "c9.ide.language.generic": "#3949510863", "c9.ide.language.html": "#22fdc74869", "c9.ide.language.html.diff": "#24f3608d26", - "c9.ide.language.javascript": "#d1a479805c", + "c9.ide.language.javascript": "#e626169643", "c9.ide.language.javascript.immediate": "#c8b1e5767a", - "c9.ide.language.javascript.eslint": "#3c57ed1720", + "c9.ide.language.javascript.eslint": "#4de5457db1", "c9.ide.language.javascript.tern": "#64ab01f271", "c9.ide.language.javascript.infer": "#18acb93a3a", "c9.ide.language.jsonalyzer": "#4b329741b1", - "c9.ide.language.codeintel": "#72157892e4", - "c9.ide.collab": "#11a0d3c5ce", + "c9.ide.language.codeintel": "#44b7cb3565", + "c9.ide.collab": "#728ad2bde2", "c9.ide.local": "#10eb45842a", "c9.ide.find": "#e33fbaed2f", "c9.ide.find.infiles": "#c0a13737ef", - "c9.ide.find.replace": "#8cbce45290", - "c9.ide.run.debug": "#286975f644", + "c9.ide.find.replace": "#810ebf8bfb", + "c9.ide.run.debug": "#ef40edcc3f", "c9.automate": "#47e2c429c9", "c9.ide.ace.emmet": "#6dc4585e02", "c9.ide.ace.gotoline": "#a8ff07c8f4", @@ -80,24 +80,24 @@ "c9.ide.ace.repl": "#4b88a85b7b", "c9.ide.ace.split": "#0ae0151c78", "c9.ide.ace.statusbar": "#3aab0b67e0", - "c9.ide.ace.stripws": "#8885016b9e", + "c9.ide.ace.stripws": "#042a9936e3", "c9.ide.behaviors": "#db32109ebc", "c9.ide.closeconfirmation": "#cee4674141", - "c9.ide.configuration": "#528234d97d", + "c9.ide.configuration": "#a936df26bb", "c9.ide.dialog.wizard": "#7667ec79a8", "c9.ide.fontawesome": "#781602c5d8", "c9.ide.format": "#5ec97fb083", - "c9.ide.help.support": "#af5c4055b2", + "c9.ide.help.support": "#932fbb3743", "c9.ide.imgeditor": "#612e75ef4f", "c9.ide.immediate": "#19758abe08", - "c9.ide.installer": "#0fde9f0067", + "c9.ide.installer": "#1232d4e179", "c9.ide.language.python": "#701bd2630a", "c9.ide.language.go": "#6ce1c7a7ef", "c9.ide.mount": "#4c39359b87", - "c9.ide.navigate": "#1fbb7cd53b", + "c9.ide.navigate": "#0b7ec7936c", "c9.ide.newresource": "#981a408a7b", "c9.ide.openfiles": "#2ae85a9e33", - "c9.ide.preview": "#70bd68740c", + "c9.ide.preview": "#5f5fff0185", "c9.ide.preview.browser": "#897177be7f", "c9.ide.preview.markdown": "#c3174d86e0", "c9.ide.pubsub": "#a85fb27eca", @@ -105,19 +105,19 @@ "c9.ide.recentfiles": "#7c099abf40", "c9.ide.remote": "#301d2ab519", "c9.ide.processlist": "#2b12cd1bdd", - "c9.ide.run": "#4f0257bc0b", + "c9.ide.run": "#6bd4996a4e", "c9.ide.run.build": "#0598fff697", "c9.ide.run.debug.xdebug": "#9956689819", - "c9.ide.save": "#86f0f38160", - "c9.ide.scm": "#f1d190fa51", - "c9.ide.terminal.monitor": "#1a4092ede2", - "c9.ide.test": "#a282ec1619", + "c9.ide.save": "#4cda35bfdb", + "c9.ide.scm": "#637a68cd04", + "c9.ide.terminal.monitor": "#affa33572f", + "c9.ide.test": "#102942ae4e", "c9.ide.test.mocha": "#fc053b23d2", "c9.ide.theme.flat": "#81dadeee55", "c9.ide.threewaymerge": "#229382aa0b", "c9.ide.undo": "#b028bcb4d5", - "c9.ide.upload": "#0bd010d3dc", + "c9.ide.upload": "#a3da59803d", "c9.ide.welcome": "#5b86c44e92", - "c9.ide.guide": "#16537f57cf" + "c9.ide.guide": "#8ab966f344" } } \ No newline at end of file diff --git a/plugins/c9.cli.bridge/bridge-client.js b/plugins/c9.cli.bridge/bridge-client.js index f27353be..c5325542 100644 --- a/plugins/c9.cli.bridge/bridge-client.js +++ b/plugins/c9.cli.bridge/bridge-client.js @@ -59,7 +59,7 @@ define(function(require, exports, module) { if (done) return; callback(new Error("No Response")); done = true; - }) + }); }); } diff --git a/plugins/c9.cli.bridge/bridge_commands.js b/plugins/c9.cli.bridge/bridge_commands.js index 1952f719..f078cece 100644 --- a/plugins/c9.cli.bridge/bridge_commands.js +++ b/plugins/c9.cli.bridge/bridge_commands.js @@ -2,7 +2,7 @@ define(function(require, exports, module) { main.consumes = [ "Plugin", "bridge", "tabManager", "panels", "tree.favorites", "tree", - "fs", "preferences", "settings", "c9" + "fs", "preferences", "settings", "c9", "commands" ]; main.provides = ["bridge.commands"]; return main; @@ -18,6 +18,7 @@ define(function(require, exports, module) { var fs = imports.fs; var c9 = imports.c9; var prefs = imports.preferences; + var commands = imports.commands; var async = require("async"); @@ -36,6 +37,15 @@ define(function(require, exports, module) { case "open": open(message, e.respond); break; + case "exec": + exec(message, e.respond); + break; + case "pipe": + createPipe(message, e.respond); + break; + case "pipeData": + updatePipe(message, e.respond); + break; case "ping": e.respond(null, true); break; @@ -66,6 +76,30 @@ define(function(require, exports, module) { } /***** Methods *****/ + function createPipe(message, callback) { + tabManager.once("ready", function(){ + tabManager.open({ + focus: true, + editorType: "ace", + path: message.path && c9.toInternalPath(message.path), + document: { meta : { newfile: true } } + }, function(err, tab) { + if (err) + return callback(err); + callback(null, tab.path || tab.name); + }); + }); + } + + function updatePipe(message, callback) { + tabManager.once("ready", function() { + var tab = tabManager.findTab(message.tab); + var c9Session = tab && tab.document.getSession(); + if (c9Session && c9Session.session) + c9Session.session.insert({row: Number.MAX_VALUE, column: Number.MAX_VALUE} , message.data); + callback(null, true); + }); + } function open(message, callback) { var i = -1; @@ -102,12 +136,22 @@ define(function(require, exports, module) { } else { tabManager.once("ready", function(){ + var m = /:(\d*)(?::(\d*))?$/.exec(path); + var jump = {}; + if (m) { + if (m[1]) + jump.row = parseInt(m[1], 10) - 1; + if (m[2]) + jump.column = parseInt(m[2], 10); + path = path.slice(0, m.index); + } + fs.exists(path, function(existing) { var tab = tabManager.open({ path: path, focus: i === 0, document: existing - ? undefined + ? { ace: { jump: jump } } : { meta : { newfile: true } } }, function(){ next(); @@ -133,6 +177,12 @@ define(function(require, exports, module) { callback(null, true); }); } + + function exec(message, callback) { + var result = commands.exec(message.command, message.args); + var err = result ? null : "command failed"; + callback(err, result); + } /***** Lifecycle *****/ diff --git a/plugins/c9.cli.exec/exec.js b/plugins/c9.cli.exec/exec.js new file mode 100644 index 00000000..e8a48f4b --- /dev/null +++ b/plugins/c9.cli.exec/exec.js @@ -0,0 +1,92 @@ +define(function(require, exports, module) { + main.consumes = ["Plugin", "cli_commands", "bridge.client"]; + main.provides = ["exec"]; + return main; + + function main(options, imports, register) { + var Plugin = imports.Plugin; + var cmd = imports.cli_commands; + var bridge = imports["bridge.client"]; + + /***** Initialization *****/ + + var plugin = new Plugin("Ajax.org", main.consumes); + // var emit = plugin.getEmitter(); + + var loaded; + function load(){ + if (loaded) return; + loaded = true; + + cmd.addCommand({ + name: "exec", + info: " Executes remote c9 commands.", + usage: " [argument 1] [argument 2] ... [argument n]", + check: function(argv) { + if (argv._.length < 2) + throw new Error("Missing command"); + }, + options: {}, + exec: function(argv) { + exec( + argv._[1], + argv._.slice(2), + function(){}); + } + }); + } + + /***** Methods *****/ + + function exec(command, args, callback) { + args.unshift(process.cwd()); + var message = { + type: "exec", + command: command, + args: args + }; + + bridge.send(message, function cb(err, response) { + if (err) { + console.log(err.message); + } + + if (response !== true) + console.log("Could not execute", command); + + process.exit(); // I don't get why this is needed + }); + } + + /***** Lifecycle *****/ + + plugin.on("load", function(){ + load(); + }); + plugin.on("enable", function(){ + + }); + plugin.on("disable", function(){ + + }); + plugin.on("unload", function(){ + loaded = false; + }); + + /***** Register and define API *****/ + + /** + * Finds or lists files and/or lines based on their filename or contents + **/ + plugin.freezePublicAPI({ + /** + * + */ + exec: exec + }); + + register(null, { + exec: plugin + }); + } +}); \ No newline at end of file diff --git a/plugins/c9.cli.open/open.js b/plugins/c9.cli.open/open.js index d186f500..5b8b68da 100755 --- a/plugins/c9.cli.open/open.js +++ b/plugins/c9.cli.open/open.js @@ -25,19 +25,28 @@ define(function(require, exports, module) { cmd.addCommand({ name: "open", info: " Opens a file or directory.", - usage: "[--wait] ", + usage: "[--wait] [--pipe] ", options: { "wait": { description: "Wait until the file(s) are closed", "default": false, "boolean": true + }, + "pipe": { + description: "Pipe data from a command into c9", + "default": false, + "boolean": true } }, check: function(argv) { - if (argv._.length < 2 && !argv["path"]) + if (argv._.length < 2 && !argv["path"] && !argv.pipe) throw new Error("Missing path"); }, exec: function(argv) { + if (argv.pipe) { + openWithPipe(function(){}); + return; + } open( argv._.slice(1), // Remove "open" from the paths argv.wait, @@ -126,6 +135,60 @@ define(function(require, exports, module) { }); } + function openWithPipe(callback) { + bridge.send({ + type: "pipe", + path: process.cwd() + "/" + "Pipe " + (new Date()).toLocaleString().replace(/:/g, "."), + }, function cb(err, response) { + if (err) { + if (err.code == "ECONNREFUSED") { + // Seems Cloud9 is not running, lets start it up + startCloud9Local({}, function(success) { + if (success) + bridge.send({ type: "pipe" }, cb); + else { + console.log("Could not start Cloud9. " + + "Please check your configuration."); + callback(err); + + process.exit(40); // This appears to be needed; let's return something useful + } + }); + return; + } + else { + console.log(err.message); + return; + } + } + + var stdin = process.openStdin(); + stdin.setEncoding("utf8"); + var finished = 0; + stdin.on("data", function(chunk) { + finished++; + bridge.send({ + type: "pipeData", + data: chunk, + tab: response + }, function(err, message) { + // Dunno why, but this always returns No Response... + // Escaping that error so end users aren't confused... + if (err && err.message !== "No Response") + console.log(err.message); + finished--; + }); + }); + stdin.on("end", function() { + (function retry() { + if (finished === 0) + process.exit(); + setTimeout(retry, 100); + })(); + }); + }); + } + function startCloud9Local(opts, callback) { if (options.platform == "darwin") { proc.spawn("open", { diff --git a/plugins/c9.cli.publish/publish.js b/plugins/c9.cli.publish/publish.js index d3efede7..3c8551c9 100644 --- a/plugins/c9.cli.publish/publish.js +++ b/plugins/c9.cli.publish/publish.js @@ -653,6 +653,10 @@ define(function(require, exports, module) { p = "/" + normalizePath(Path.relative(cwd, p)); excludeMap[p] = 1; }); + // keep installer in both packed and unpacked form + if (json.installer) + excludeMap["/" + normalizePath(Path.relative(cwd, json.installer))] = 0; + copy(cwd, cwd + "/.c9/.build", { exclude: function(name, parent) { if (excludeRe.test(name)) diff --git a/plugins/c9.core/ext.js b/plugins/c9.core/ext.js index d8a1e866..5768c28a 100644 --- a/plugins/c9.core/ext.js +++ b/plugins/c9.core/ext.js @@ -577,7 +577,7 @@ define(function(require, exports, module) { function setAPIKey(apikey){ // Validate Key - if (!apikey || !apikey.match(/[\w+]{27}=/)) + if (!apikey || !apikey.match(/^.{27}=$/)) throw new Error("Invalid API key"); return { diff --git a/plugins/c9.error/error_handler.js b/plugins/c9.error/error_handler.js index 1c46ee68..4e3fe2af 100644 --- a/plugins/c9.error/error_handler.js +++ b/plugins/c9.error/error_handler.js @@ -115,6 +115,7 @@ function plugin(options, imports, register) { var accept = req.headers.accept || ''; if (statusCode == 500) { + console.error(err && err.stack); emitter.emit("internalServerError", { err: err, req: req diff --git a/plugins/c9.error/raygun.connect.js b/plugins/c9.error/raygun.connect.js index 512be225..922eae90 100644 --- a/plugins/c9.error/raygun.connect.js +++ b/plugins/c9.error/raygun.connect.js @@ -90,6 +90,7 @@ function plugin(options, imports, register) { var customData = _.clone(raygun.customData || {}); if (req.user) { customData.user = { + augment: err.augment, id: req.user.id, name: req.user.name, email: req.user.email diff --git a/plugins/c9.error/raygun.js b/plugins/c9.error/raygun.js index 6a51ce0b..ca753d4a 100644 --- a/plugins/c9.error/raygun.js +++ b/plugins/c9.error/raygun.js @@ -25,8 +25,8 @@ function plugin(options, imports, register) { }; var clients = { - error: new raygun.Client().init({ apiKey: options.keys.error }), - warning: new raygun.Client().init({ apiKey: options.keys.warning }) + error: new raygun.Client().init({ apiKey: options.keys.error, useHumanStringForObject: false }), + warning: new raygun.Client().init({ apiKey: options.keys.warning, useHumanStringForObject: false }) }; var customClients = options.customClients || {}; diff --git a/plugins/c9.error/views/error-503.html.ejs b/plugins/c9.error/views/error-503.html.ejs index 86c37845..eb7394ba 100644 --- a/plugins/c9.error/views/error-503.html.ejs +++ b/plugins/c9.error/views/error-503.html.ejs @@ -1,7 +1,7 @@ - <%=error%> + 503 - Service Unavailable diff --git a/plugins/c9.fs/fs.cache.xml.js b/plugins/c9.fs/fs.cache.xml.js index 34b693b2..7922f9a0 100644 --- a/plugins/c9.fs/fs.cache.xml.js +++ b/plugins/c9.fs/fs.cache.xml.js @@ -82,6 +82,9 @@ define(function(require, exports, module) { node.status = "pending"; return; } + var parentPath = e.path; + if (!parentPath.endsWith("/")) + parentPath += "/"; // update cache if (!node) { if (!showHidden && isFileHidden(e.path)) @@ -92,97 +95,116 @@ define(function(require, exports, module) { orphans[e.path] = node; orphan = true; } + node.$lastReadT = Date.now(); // Indicate this directory has been fully read model.setAttribute(node, "status", "loaded"); - var wasOpen = startUpdate(node); - node.children = null; - var existing = node.map || {}; - node.map = {}; - - // Fill Parent - var ondisk = {}, toAppend = []; + var ondisk = Object.create(null); + var toRemove = []; + var toCreate = []; + var orphanAppand = []; + var existing = node.map || (node.map = Object.create(null)); e.result[1].forEach(function(stat) { if (!stat.name || !showHidden && isFileHidden(stat.name)) return; - var name = stat.name; - var path = (e.path + "/" + name).replace("//", "/"); + var path = parentPath + name; ondisk[name] = 1; - // if (existing[name]) return; + if (orphans[path]) { - toAppend.push(path); + if (existing[name]) + delete orphans[path]; + orphanAppand.push(path); } - createNode(path, stat, existing[name], true); + + if (existing[name]) + updateNodeStat(path, stat, existing[name]); + else + toCreate.push(stat); }); - for (var name in existing) { - if (!ondisk[name]) { - // onreaddir can be called before copied nodes are written to disk - // in this case we don't want to lose "predicted" state - if (existing[name] && existing[name].status === "predicted") - node.map[name] = existing[name]; - else { - delete existing[name]; - - emit("remove", { - path: e.path + "/" + name, - node: existing[name], - parent: node - }); - } - } - } + Object.keys(existing).forEach(function(name) { + // onreaddir can be called before copied nodes are written to disk + // in this case we don't want to lose "predicted" state + if (existing[name] && existing[name].status === "predicted") + ondisk[name] = 1; + if (!ondisk[name]) + toRemove.push(name); + }); + + if (!toCreate.length && !toRemove.length && !orphanAppand.length) + return; + + var wasOpen = startUpdate(node); + node.children = null; + + // Fill Parent + toCreate.forEach(function(stat) { + createNode(parentPath + stat.name, stat, null, true); + }); + + toRemove.forEach(function(name) { + var currentNode = existing[name]; + delete existing[name]; + emit("remove", { + path: parentPath + name, + node: currentNode, + parent: node + }); + }); emit("readdir", { path : e.path, parent : node, orphan: orphan }); endUpdate(node, wasOpen); - toAppend.forEach(function(path) { + orphanAppand.forEach(function(path) { emit("orphan-append", {path: path}); }); } fs.on("afterReaddir", onreaddir, plugin); function onstat(e) { - var stat; + if (e.error) return; - if (!e.error) { - // update cache - var there = true; - var node = findNode(e.path); - var parent = findNode(dirname(e.path)); - - if (!showHidden && isFileHidden(e.path)) - return; + // update cache + var stat = e.result[1]; + + var there = true; + var node = findNode(e.path); + var parent = findNode(dirname(e.path)); + + if (!showHidden && isFileHidden(e.path)) + return; - if (!node) { - if (!parent) - return; - there = false; + if (!node) { + if (!parent) + return; + there = false; + } + + if (there != !!stat) { + if (there) { + if (!node.link) + deleteNode(node); } - - if (there != !!e.result[1]) { - if (there) { - if (!node.link) - deleteNode(node); - } - else { - stat = e.result[1]; - if (typeof stat != "object") - stat = null; - createNode(e.path, stat); - } - } - else if (there) { - stat = e.result[1]; + else { if (typeof stat != "object") stat = null; - createNode(e.path, stat, node); + createNode(e.path, stat); } } + else if (there) { + if (typeof stat != "object") + stat = null; + if (!stat && node) + return; + if (stat && node) + updateNodeStat(e.path, stat, node); + else + createNode(e.path, stat, node); + } } fs.on("afterStat", onstat, plugin); fs.on("afterReadFile", function(e) { @@ -295,14 +317,15 @@ define(function(require, exports, module) { // Validation var toNode = findNode(newPath); - deleteNode(node, true); - if (toNode) - deleteNode(toNode, true); - - createNode(newPath, null, node); // Move node - recurPathUpdate(node, oldPath, newPath); + if (!toNode) { + deleteNode(node, true); + createNode(newPath, null, node); // Move node + recurPathUpdate(node, oldPath, newPath); + } e.undo = function(){ + if (toNode) + return; if (!parent) { var tmpParent = node; while (node.parent && tmpParent.parent.status == "pending") @@ -318,6 +341,12 @@ define(function(require, exports, module) { recurPathUpdate(node, newPath, oldPath); }; e.confirm = function() { + if (toNode) { + deleteNode(toNode, true); + createNode(newPath, null, node); // Move node + recurPathUpdate(node, oldPath, newPath); + } + if (node.status === "predicted") node.status = "loaded"; }; @@ -511,14 +540,8 @@ define(function(require, exports, module) { updateNode = orphans[path]; delete orphans[path]; } - var original_stat; - if (stat && stat.link) { - original_stat = stat; - stat = stat.linkStat; - } var parts = path.split("/"); - var name = parts[parts.length - 1]; var node = model.root.map[parts[0] == "~" ? "~" : ""]; if (!node) { node = orphans[parts[0]]; @@ -537,7 +560,7 @@ define(function(require, exports, module) { var map = node.map; if (!map) { - map = node.map = {}; + map = node.map = Object.create(null); } parent = node; node = map[p]; @@ -545,6 +568,11 @@ define(function(require, exports, module) { modified.push(parent); if (i !== parts.length - 1) { node = {label: p, path: subPath, status: "pending", isFolder: true}; + // TODO filter hidden files in getChildren instead. + if (!showHidden && isFileHidden(p)) { + orphans[node.path] = path; + return; + } } else if (updateNode) { deleteNode(updateNode, true); node = updateNode; @@ -564,8 +592,32 @@ define(function(require, exports, module) { node = {label: parts[parts.length - 1], path: path}; orphans[path] = node; } + + updateNodeStat(path, stat, node); + + node.children = null; + + if (!updating) { + if (!modified.length) + modified.push(parent); + var wasOpen = startUpdate(modified[0]); + modified.forEach(function(n) { + if (n != model.root) + n.children = null; + }); + endUpdate(modified[0], wasOpen); + } + model._signal("createNode", node); + return node; + } + + function updateNodeStat(path, stat, node) { node.path = path; - + var original_stat; + if (stat && stat.link) { + original_stat = stat; + stat = stat.linkStat; + } if (stat) { var isFolder = stat && /(directory|folder)$/.test(stat.mime); if (isFolder) { @@ -589,25 +641,13 @@ define(function(require, exports, module) { delete node.isFolder; } - if (node.isFolder && !node.map) - node.map = {}; - else if (!node.isFolder && node.map) + if (node.isFolder && !node.map) { + node.map = Object.create(null); + node.children = null; + } else if (!node.isFolder && node.map) { delete node.map; - - node.children = null; - - if (!updating) { - if (!modified.length) - modified.push(parent); - var wasOpen = startUpdate(modified[0]); - modified.forEach(function(n) { - if (n != model.root) - n.children = null; - }); - endUpdate(modified[0], wasOpen); + node.children = null; } - model._signal("createNode", node); - return node; } function deleteNode(node, silent) { @@ -670,7 +710,7 @@ define(function(require, exports, module) { map: {} }; var root = {}; - root.map = {}; + root.map = Object.create(null); root.map[""] = model.projectDir; model.setRoot(root); // fs.readdir("/", function(){}); @@ -894,7 +934,12 @@ define(function(require, exports, module) { * @param {Function} progress * @param {Function} done */ - loadNodes: loadNodes + loadNodes: loadNodes, + + /** + * @ignore + */ + isFileHidden: isFileHidden }); register(null, { diff --git a/plugins/c9.fs/proc_test.js b/plugins/c9.fs/proc_test.js index db68c57e..96be5406 100644 --- a/plugins/c9.fs/proc_test.js +++ b/plugins/c9.fs/proc_test.js @@ -37,7 +37,7 @@ require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) describe('proc', function() { describe('spawn()', function() { - this.timeout(4000); + this.timeout(10000); it("should spawn a child process", function(done) { var args = ["-e", "process.stdin.pipe(process.stdout);try{process.stdin.resume()}catch(e) {};"]; @@ -66,8 +66,8 @@ require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) }); }); - //should test the kill() method - which is broken now - //Another test - see that cwd defaults to the root vfs dir when resolve is set to true + // should test the kill() method - which is broken now + // Another test - see that cwd defaults to the root vfs dir when resolve is set to true }); describe('execFile()', function() { this.timeout(10000); @@ -101,8 +101,19 @@ require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) }); }); - //should test the kill() method - which is broken now - //Another test - see that cwd defaults to the root vfs dir when resolve is set to true + it('should pass stdout and stderr', function(done) { + proc.execFile("node", { + args: ["-v"] + }, function(e, stdout, stderr) { + expect(stdout[0]).to.equal("v"); + expect(stderr).to.equal(""); + expect(e).to.not.ok; + done(); + }); + }); + + // should test the kill() method - which is broken now + // Another test - see that cwd defaults to the root vfs dir when resolve is set to true }); describe('pty()', function() { this.timeout(30000); diff --git a/plugins/c9.ide.ace/ace.js b/plugins/c9.ide.ace/ace.js index ff712657..b2da9718 100644 --- a/plugins/c9.ide.ace/ace.js +++ b/plugins/c9.ide.ace/ace.js @@ -1444,7 +1444,7 @@ define(function(require, exports, module) { else if (/^{/.test(firstLine)) { syntax = "json"; } - else if (/\.(bashrc|inputrc)$/.test(path)) { + else if (/\.(bash|inputrc|profile|zsh)/.test(path)) { syntax = "sh"; } else if (/\.(git(attributes|config|ignore)|npmrc)$/.test(path)) { diff --git a/plugins/c9.ide.dialog.common/alert_internal.js b/plugins/c9.ide.dialog.common/alert_internal.js index 572c48ad..98744ee1 100644 --- a/plugins/c9.ide.dialog.common/alert_internal.js +++ b/plugins/c9.ide.dialog.common/alert_internal.js @@ -26,6 +26,8 @@ define(function(require, module, exports) { /***** Methods *****/ function show(title, header, msg, onhide, options) { + options = options || {}; + metrics.increment("dialog.error"); return plugin.queue(function(){ @@ -37,13 +39,15 @@ define(function(require, module, exports) { else { plugin.title = title; } - plugin.heading = options && options.isHTML ? header : util.escapeXml(header); - plugin.body = options && options.isHTML ? msg : (util.escapeXml(msg) || "") + plugin.heading = options.isHTML ? header : util.escapeXml(header); + plugin.body = options.isHTML ? msg : (util.escapeXml(msg) || "") .replace(/\n/g, "
") .replace(/(https?:\/\/[^\s]*\b)/g, "$1"); + plugin.getElement("ok").setCaption(options.yes || "OK"); + plugin.update([ - { id: "dontshow", visible: options && options.showDontShow } + { id: "dontshow", visible: options.showDontShow } ]); plugin.once("hide", function(){ diff --git a/plugins/c9.ide.dialog.common/question.js b/plugins/c9.ide.dialog.common/question.js index 7e3be92e..23d08df1 100644 --- a/plugins/c9.ide.dialog.common/question.js +++ b/plugins/c9.ide.dialog.common/question.js @@ -54,7 +54,7 @@ define(function(require, module, exports) { var gotYesNo = false; plugin.once("hide", function(){ - !gotYesNo && cancel && onNo(false, true, metadata); + !gotYesNo && cancel && onNo && onNo(false, true, metadata); }); plugin.update([ @@ -63,22 +63,22 @@ define(function(require, module, exports) { { id: "yestoall", visible: all, onclick: function(){ gotYesNo = true; plugin.hide(); - onYes(true, metadata); + onYes && onYes(true, metadata); }}, { id: "notoall", visible: all, onclick: function(){ gotYesNo = true; plugin.hide(); - onNo(true, false, metadata); + onNo && onNo(true, false, metadata); }}, { id: "yes", onclick: function(){ gotYesNo = true; plugin.hide(); - onYes(false, metadata); + onYes && onYes(false, metadata); }}, { id: "no", onclick: function(){ gotYesNo = true; plugin.hide(); - onNo(false, false, metadata); + onNo && onNo(false, false, metadata); }} ]); }, options.queue === false); diff --git a/plugins/c9.ide.dialog.common/upsell.js b/plugins/c9.ide.dialog.common/upsell.js index 0200a9ba..1b5af324 100644 --- a/plugins/c9.ide.dialog.common/upsell.js +++ b/plugins/c9.ide.dialog.common/upsell.js @@ -27,14 +27,14 @@ define(function(require, module, exports) { options = {isHTML: true}; return plugin.queue(function(){ - var all = options.all; var cancel = options.cancel; - var showDontAsk = options.showDontAsk; var metadata = options.metadata; title = title || "This is a Premium feature"; - header = header || "Get Premium Support Now!"; - msg = msg || 'Help is just a few clicks away. Check out our amazing premium plans.' + header = header || "Upgrade to Premium Now!"; + onYes = onYes || function() {}; + onNo = onNo || function() {}; + msg = msg || 'A better, faster, more versatile Cloud9 is just a click away. Check out our amazing premium plans.'; plugin.title = title; plugin.heading = options && options.isHTML ? header : util.escapeXml(header); @@ -73,7 +73,12 @@ define(function(require, module, exports) { plugin.freezePublicAPI({ /** - * + * @param {Function} onYes Callback for when user clicks the 'yes' button + * @param {Function} onNo Callback for when the user clicks the 'no' button + * @param {String} [title] Title for the dialog + * @param {String} [header] Header for the dialog body + * @param {String} [msg] Message to show the user. + * @param {Object} [options] Miscellaneous options */ show: show }); diff --git a/plugins/c9.ide.dialog.file/file.xml b/plugins/c9.ide.dialog.file/file.xml index 590bd4f5..dd4b341e 100644 --- a/plugins/c9.ide.dialog.file/file.xml +++ b/plugins/c9.ide.dialog.file/file.xml @@ -27,7 +27,7 @@ @@ -46,7 +46,7 @@ diff --git a/plugins/c9.ide.dialog.login/login.xml b/plugins/c9.ide.dialog.login/login.xml index 82b954e8..aff22bd0 100644 --- a/plugins/c9.ide.dialog.login/login.xml +++ b/plugins/c9.ide.dialog.login/login.xml @@ -28,7 +28,7 @@ @@ -41,7 +41,7 @@ diff --git a/plugins/c9.ide.dialog/dialog.js b/plugins/c9.ide.dialog/dialog.js index bf5d15e3..77f36633 100644 --- a/plugins/c9.ide.dialog/dialog.js +++ b/plugins/c9.ide.dialog/dialog.js @@ -245,14 +245,16 @@ define(function(require, module, exports) { dropdown.setAttribute("value", item.value); break; default: - if ("value" in item) - el.setAttribute('value', item.value); - if ("onclick" in item) - el.onclick = item.onclick; - if ("visible" in item) - el.setAttribute("visible", item.visible); - if ("zindex" in item) - el.setAttribute("zindex", item.zindex); + // supported attributes + var validAttributes = /^(value|visible|zindex|disabled|caption|tooltip|command|class|icon|src|submenu)$/; + Object.keys(item).forEach(function(key) { + // Check for onclick explictly + if (key === "onclick") + return el.onclick = item.onclick; + // Check for attributes we know exist and will directly set + if (validAttributes.test(key)) + return el.setAttribute(key, item[key]); + }); break; } }); diff --git a/plugins/c9.ide.editors/tab.js b/plugins/c9.ide.editors/tab.js index 5457b4c6..d1692979 100644 --- a/plugins/c9.ide.editors/tab.js +++ b/plugins/c9.ide.editors/tab.js @@ -291,9 +291,9 @@ define(function(require, module, exports) { load(); }); - plugin.on("beforeUnload", function(){ + plugin.on("beforeUnload", function(e){ if (!plugin.meta.$closing) { - if (close()) + if (close(e && e.animate === false)) return false; } }); diff --git a/plugins/c9.ide.editors/tabmanager.js b/plugins/c9.ide.editors/tabmanager.js index da270a71..caf097d3 100644 --- a/plugins/c9.ide.editors/tabmanager.js +++ b/plugins/c9.ide.editors/tabmanager.js @@ -1283,7 +1283,7 @@ define(function(require, module, exports) { // Or keep tab until the new one is loaded else { - previewTab.unload(); + previewTab.unload({ animate: false }); } } diff --git a/plugins/c9.ide.info/info.js b/plugins/c9.ide.info/info.js index c3ad0b6b..5df32577 100644 --- a/plugins/c9.ide.info/info.js +++ b/plugins/c9.ide.info/info.js @@ -74,12 +74,15 @@ define(function(require, exports, module) { plugin.freezePublicAPI({ /** * Returns the logged in user. + * + * @param [callback] * @return {Object} The currently user */ getUser: getUser, /** * Return the active workspace. + * * @return {Object} The currently active workspace */ getWorkspace: getWorkspace, diff --git a/plugins/c9.ide.keys/commands.js b/plugins/c9.ide.keys/commands.js index 0cab430e..5cae4001 100644 --- a/plugins/c9.ide.keys/commands.js +++ b/plugins/c9.ide.keys/commands.js @@ -96,9 +96,18 @@ define(function(require, exports, module) { } function getHotkey(command) { + if (!commands[command] || !commands[command].bindKey) + return ""; return commands[command].bindKey[platform]; } + function getPrettyHotkey(command) { + var key = getHotkey(command); + if (platform == "mac") + key = apf.hotkeys.toMacNotation(key); + return key; + } + var markDirty = lang.delayedCall(function(){ emit("update"); }, 500); @@ -557,6 +566,14 @@ define(function(require, exports, module) { */ getHotkey: getHotkey, + /** + * returns result of getHotkey formatted for displaying in menus + * + * @param {String} name the name of the command. + * @return {String} + */ + getPrettyHotkey: getPrettyHotkey, + /** * Executes the action tied to a command. This method will call * the `isAvailable` method for a command and will not execute if diff --git a/plugins/c9.ide.keys/panel.js b/plugins/c9.ide.keys/panel.js index a2d1cf0f..8f102d52 100644 --- a/plugins/c9.ide.keys/panel.js +++ b/plugins/c9.ide.keys/panel.js @@ -79,6 +79,9 @@ define(function(require, exports, module) { // @TODO this is probably not sufficient layout.on("resize", function(){ tree.resize() }, plugin); + var key = commands.getPrettyHotkey("commands"); + txtFilter.setAttribute("initial-message", key); + tree.textInput = txtFilter.ace.textInput; txtFilter.ace.commands.addCommands([ diff --git a/plugins/c9.ide.layout.classic/layout.js b/plugins/c9.ide.layout.classic/layout.js index d2789759..250fbf9f 100644 --- a/plugins/c9.ide.layout.classic/layout.js +++ b/plugins/c9.ide.layout.classic/layout.js @@ -214,7 +214,7 @@ define(function(require, exports, module) { } } - function proposeLayoutChange(kind, force, type, reset) { + function proposeLayoutChange(kind, force, type) { if (!force && settings.getBool("user/general/@propose")) return; @@ -225,7 +225,7 @@ define(function(require, exports, module) { ignoreTheme = true; var theme = {"dark": "flat-dark", "light": "flat-light"}[kind]; settings.set("user/general/@skin", theme); - updateTheme(!!reset, type); + updateTheme(false, type); ignoreTheme = false; settings.set("user/general/@propose", question.dontAsk); }, @@ -333,15 +333,6 @@ define(function(require, exports, module) { amlNode.$ext.className += " c9btn"; menus.addItemByPath("File/~", new apf.divider(), 1000000, plugin); - - if (!c9.local) { - menus.addItemByPath("Cloud9/~", new apf.divider(), 2000000, plugin); - menus.addItemByPath("Cloud9/Quit Cloud9", new apf.item({ - onclick: function(){ - location.href = "http://c9.io"; - } - }), 2000100, plugin); - } menus.addItemByPath("View/~", new apf.divider(), 9999, plugin); @@ -357,6 +348,14 @@ define(function(require, exports, module) { }), 300, plugin); } + function resetTheme(theme, type) { + ignoreTheme = true; + settings.set("user/general/@skin", theme); + updateTheme(true); + emit("themeDefaults", {theme: theme, type: type}); + ignoreTheme = false; + } + function resize(){ if (c9console && tabManager) { var tRect = tabManager.container.$ext.getBoundingClientRect(); @@ -597,7 +596,7 @@ define(function(require, exports, module) { get theme(){ return theme; }, - + /** * Returns an AMLElement that can server as a parent. * @param {Plugin} plugin The plugin for which to find the parent. @@ -613,6 +612,13 @@ define(function(require, exports, module) { */ initMenus: initMenus, + /** + * Resets theme (without questioning user). + * @param {String} theme Theme to use. + * @param {String} type Type of editor to use. + */ + resetTheme: resetTheme, + /** * Sets the layout in one of two default modes: * @param {"default"|"minimal"} type diff --git a/plugins/c9.ide.layout.classic/less/btn-switcher.less b/plugins/c9.ide.layout.classic/less/btn-switcher.less new file mode 100644 index 00000000..74e4a5cc --- /dev/null +++ b/plugins/c9.ide.layout.classic/less/btn-switcher.less @@ -0,0 +1,52 @@ +.btn-switcher { + .user-select(none); + -webkit-display: flex; + display: flex; + -webkit-align-items: center; + align-items: center; + cursor: default; + font-size: @preview-chooser-font-size; + font-weight: @preview-chooser-font-weight; + color: ; + font-family: @preview-chooser-font-family; + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; + background: @preview-chooser-background; + border-radius: 0 2px 2px 0; + box-shadow: @preview-chooser-box-shadow; + cursor: pointer; + border: 1px solid @textbox-border-color; + border-left : 0; + border-radius: 0 3px 3px 0; + position : relative; + + padding: 4px 17px 4px 8px; + box-sizing: border-box; +} +.btn-switcherOver{ + background: @preview-chooser-over-background; +} +.btn-switcherDown{ + background: @preview-chooser-active-background; +} +.btn-switcher svg{ + vertical-align: middle; + margin: -2px 4px 0 0; +} + +.btn-switcher span{ + display : block; + position : absolute; + right : 6px; + top : 11px; + width : 5px; + height : 5px; + background: url("@{image-path}/@{preview-chooser-arrow}") no-repeat; + opacity: 0.6; +} + +.btn-switcherIcon { + background-repeat: no-repeat; + padding-left: 25px; + background-position: 5px 50% +} \ No newline at end of file diff --git a/plugins/c9.ide.layout.classic/less/codebox.less b/plugins/c9.ide.layout.classic/less/codebox.less index 129aeee6..357253c1 100644 --- a/plugins/c9.ide.layout.classic/less/codebox.less +++ b/plugins/c9.ide.layout.classic/less/codebox.less @@ -18,6 +18,9 @@ color : @textbox-disabled-color; background: @textbox-disabled-background; } +.has_apf .searchTxt.tb_console.ace_searchboxDisabled .sbtb_middle .input { + color : @textbox-disabled-color; +} .ace_one-line .ace_scroller.ace_scroll-left { box-shadow: none; } @@ -40,10 +43,6 @@ outline : none; text-overflow : ellipsis; } -.tb_consoleFocus .sbtb_middle, -.tb_consoleOver .sbtb_middle, -.tb_console .sbtb_middle:hover { -} .topborder .sbtb_middle{ border-width : 1px 0 0 0; @@ -63,15 +62,13 @@ padding : 0; } -.tb_console.tb_textboxInitial .sbtb_middle { - padding-right : 0 !important; + +.ace_editor .tb_textboxInitialMsg { + text-indent: 5px; + text-shadow: none; } -.tb_console.tb_textboxInitial .input { - font-size : 10px; -} - -.tb_console.tb_textboxInitial .ace_cursor{ +.ace_initialMsg:not(.ace_focus) .ace_cursor { display : none; } diff --git a/plugins/c9.ide.layout.classic/less/main.less b/plugins/c9.ide.layout.classic/less/main.less index 7c158e95..a2a4dd5d 100644 --- a/plugins/c9.ide.layout.classic/less/main.less +++ b/plugins/c9.ide.layout.classic/less/main.less @@ -47,6 +47,7 @@ BODY.noInput *{ @import "less/button.less"; @import "less/btn_console.less"; @import "less/btn_console_open.less"; +@import "less/btn-switcher.less"; @import "less/c9-divider-double.less"; @import "less/c9-divider-hor.less"; @import "less/c9-divider.less"; diff --git a/plugins/c9.ide.layout.classic/less/searchbox.less b/plugins/c9.ide.layout.classic/less/searchbox.less index 23147036..00f7ef2c 100644 --- a/plugins/c9.ide.layout.classic/less/searchbox.less +++ b/plugins/c9.ide.layout.classic/less/searchbox.less @@ -36,7 +36,7 @@ height: auto; position: relative; } -.searchTxt.tb_textboxInitial .sbtb_middle .input { +.searchTxt.tb_textboxInitial .sbtb_middle .input, .tb_textboxInitialMsg { color: @textbox-initial-color !important; text-shadow: @textbox-initial-text-shadow; font-family: @find-textbox-font-family; diff --git a/plugins/c9.ide.layout.classic/skins.xml b/plugins/c9.ide.layout.classic/skins.xml index 4fe050bc..6e78f388 100644 --- a/plugins/c9.ide.layout.classic/skins.xml +++ b/plugins/c9.ide.layout.classic/skins.xml @@ -859,4 +859,16 @@ + + + +
+ - +
+
+
+
\ No newline at end of file diff --git a/plugins/c9.ide.layout.classic/themes/default-flat-dark.less b/plugins/c9.ide.layout.classic/themes/default-flat-dark.less index 9ba1ada6..98a1e73b 100644 --- a/plugins/c9.ide.layout.classic/themes/default-flat-dark.less +++ b/plugins/c9.ide.layout.classic/themes/default-flat-dark.less @@ -1711,7 +1711,7 @@ @form-bar-border-bottom: 1px solid black; @form-bar-box-shadow: 0 1px @border-highlight; -@panel-settings-changes-top: 46px; +@panel-settings-changes-top: 53px; /*******/ diff --git a/plugins/c9.ide.layout.classic/themes/default-flat-light.less b/plugins/c9.ide.layout.classic/themes/default-flat-light.less index bc630707..74aa8f16 100644 --- a/plugins/c9.ide.layout.classic/themes/default-flat-light.less +++ b/plugins/c9.ide.layout.classic/themes/default-flat-light.less @@ -1066,8 +1066,8 @@ @textbox-border-color: darken(#dedede, @darken-chrome); @textbox-initial-color: darken(#A5A5A5, @darken-chrome); @textbox-initial-text-shadow: 0 1px 0 darken(#FFFFFF, @darken-chrome); -@textbox-disabled-color: gray; -@textbox-disabled-background: darken(rgb(216, 216, 216), @darken-chrome); +@textbox-disabled-color: #C5C5C5; +@textbox-disabled-background: #F1F1F1; // Textbox - Simple @tbsimple-border: 1px solid #DEDEDE; @@ -1706,9 +1706,9 @@ @diff-toolbar-buttons-background: #F9F9F9; @diff-toolbar-buttons-border: 1px solid @border-highlight-dark; -@form-bar-padding: 10px 50px 10px 10px; +@form-bar-padding: 10px 10px 0 10px; @form-bar-background: @menu-button-active-background; -@form-bar-border-bottom: 1px solid black; +@form-bar-border-bottom: 1px solid #DEDEDE; @form-bar-box-shadow: 0 1px @border-highlight; -@panel-settings-changes-top: 46px; \ No newline at end of file +@panel-settings-changes-top: 53px; \ No newline at end of file diff --git a/plugins/c9.ide.layout.classic/themes/flat-light.less b/plugins/c9.ide.layout.classic/themes/flat-light.less index 351b97ee..82317527 100644 --- a/plugins/c9.ide.layout.classic/themes/flat-light.less +++ b/plugins/c9.ide.layout.classic/themes/flat-light.less @@ -470,6 +470,9 @@ body .runner-form-header{ .cbblack.cbcontainerDisabled.cbcontainerChecked .checkbox{ background-position: 0 -142px; } + .cbblack.cbcontainerDisabled.cbcontainerDisabled .checkbox{ + background-position: 0 0; + } .session_btn .tab_middle:before { background-position : 0 -126px; -webkit-mask-position : 0 -95px; diff --git a/plugins/c9.ide.login/login.js b/plugins/c9.ide.login/login.js index eb9ad2df..fc8e68f0 100644 --- a/plugins/c9.ide.login/login.js +++ b/plugins/c9.ide.login/login.js @@ -40,6 +40,15 @@ define(function(require, exports, module) { }); auth.on("relogin", onReLogin); + + if (!c9.local) { + menus.addItemByPath("Cloud9/~", new apf.divider(), 2000000, plugin); + menus.addItemByPath("Cloud9/Quit Cloud9", new apf.item({ + onclick: function(){ + signout(); + } + }), 2000100, plugin); + } } /***** Methods *****/ diff --git a/plugins/c9.ide.panels/panel.js b/plugins/c9.ide.panels/panel.js index ee7be651..f1245f4b 100644 --- a/plugins/c9.ide.panels/panel.js +++ b/plugins/c9.ide.panels/panel.js @@ -147,6 +147,14 @@ define(function(require, module, exports) { mnuItem.setAttribute("hotkey", "{commands.commandManager." + options.name + "}"); + + + if (button && button.setAttribute) { + var key = commands.getPrettyHotkey(options.name); + button.setAttribute("tooltip", options.name + + (key ? " (" + key + ")" : "")); + } + return command; } @@ -382,6 +390,11 @@ define(function(require, module, exports) { */ get button(){ return button; }, + /** + * + */ + get active(){ return panels.isActive(plugin.name); }, + _events: [ /** * Fires when the panel is drawn. diff --git a/plugins/c9.ide.plugins/test.html b/plugins/c9.ide.plugins/test.html index 309727c7..328b7481 100644 --- a/plugins/c9.ide.plugins/test.html +++ b/plugins/c9.ide.plugins/test.html @@ -38,8 +38,8 @@
- - + + - \ No newline at end of file + diff --git a/plugins/c9.ide.server/views/flat-load-screen.html b/plugins/c9.ide.server/views/flat-load-screen.html index b30e268f..f0710359 100644 --- a/plugins/c9.ide.server/views/flat-load-screen.html +++ b/plugins/c9.ide.server/views/flat-load-screen.html @@ -43,7 +43,9 @@ "Press Option-Tab to go to the next IDE tab.", "Press Option-Shift-T to reopen a tab you closed.", "Press Option-T to open a new terminal at any time.", - "Press Cmd-E to search for a file by name." + "Press Cmd-E to search for a file by name.", + "Press Cmd-Shift-E to search for a function by name.", + "Press Option-S to switch between a terminal and an editor.", ]; var win_keybindings = [ @@ -54,7 +56,9 @@ "Press Ctrl-D to delete the current line of code", "Press Alt-Shift-T to reopen a tab you closed.", "Press Alt-T to open a new terminal at any time.", - "Press Ctrl-E to search for a file by name." + "Press Ctrl-E to search for a file by name.", + "Press Ctrl-Shift-E to search for a function by name.", + "Press Alt-S to switch between a terminal and an editor.", ]; var isMac = (navigator.platform == "MacIntel" || navigator.platform == "Macintosh" || navigator.platform == "MacPPC"); diff --git a/plugins/c9.ide.terminal/aceterm/input.js b/plugins/c9.ide.terminal/aceterm/input.js index 18289eb8..424afcbd 100644 --- a/plugins/c9.ide.terminal/aceterm/input.js +++ b/plugins/c9.ide.terminal/aceterm/input.js @@ -221,7 +221,7 @@ define(function(require, exports, module) { }]); ace.onPaste = function(text) { - this.send(text.replace(/\r\n/g, "\n")); + this.send(text.replace(/\r\n?|\n/g, this.session.term.convertEol ? "\n" : "\r")); }; ace.setKeyboardHandler(this); diff --git a/plugins/c9.ide.terminal/aceterm/libterm.js b/plugins/c9.ide.terminal/aceterm/libterm.js index 4f30c487..c19e8656 100644 --- a/plugins/c9.ide.terminal/aceterm/libterm.js +++ b/plugins/c9.ide.terminal/aceterm/libterm.js @@ -470,7 +470,7 @@ Terminal.prototype.writeInternal = function(data) {//TODO optimize lines if (!this.insertMode) line[this.x] = [this.curAttr, ch]; else - line[insertY].splice(this.x, 0, [this.curAttr, ch]); + line.splice(this.x, 0, [this.curAttr, ch]); break; case 0: if (this.x > 0) this.x--; @@ -481,7 +481,7 @@ Terminal.prototype.writeInternal = function(data) {//TODO optimize lines line[this.x] = [this.curAttr, ch]; line[this.x + 1] = [this.curAttr, "\x00"]; } else { - line[insertY].splice(this.x, 0, [this.curAttr, ch], [this.curAttr, ""]); + line.splice(this.x, 0, [this.curAttr, ch], [this.curAttr, ""]); } this.x++; break; diff --git a/plugins/c9.ide.tree/tree.js b/plugins/c9.ide.tree/tree.js index f130e2b4..34187998 100644 --- a/plugins/c9.ide.tree/tree.js +++ b/plugins/c9.ide.tree/tree.js @@ -3,7 +3,7 @@ define(function(require, exports, module) { "Panel", "c9", "util", "fs", "settings", "ui", "menus", "panels", "commands", "tabManager", "fs.cache", "watcher", "preferences", "clipboard", "dialog.alert", "dialog.fileremove", - "dialog.fileoverwrite", "dialog.error", "layout" + "dialog.fileoverwrite", "dialog.error", "layout", "dialog.question" ]; main.provides = ["tree"]; return main; @@ -24,6 +24,7 @@ define(function(require, exports, module) { var watcher = imports.watcher; var prefs = imports.preferences; var alert = imports["dialog.alert"].show; + var question = imports["dialog.question"].show; var fsCache = imports["fs.cache"]; var confirmRemove = imports["dialog.fileremove"].show; var confirmRename = imports["dialog.fileoverwrite"].show; @@ -34,7 +35,7 @@ define(function(require, exports, module) { var TreeEditor = require("ace_tree/edit"); var markup = require("text!./tree.xml"); - var basename = require("path").basename; + var join = require("path").join; var dirname = require("path").dirname; var staticPrefix = options.staticPrefix; @@ -52,7 +53,7 @@ define(function(require, exports, module) { }); var emit = plugin.getEmitter(); - var container, winFilesViewer; //UI elements + var container, winFilesViewer; // UI elements var showHideScrollPos, scrollTimer; var tree; @@ -404,15 +405,10 @@ define(function(require, exports, module) { emit("expand", { path: id }); - if (node.justLoaded) { - delete node.justLoaded; - return; - } - // Only save if we are not loading the tree if (!refreshing || loadedSettings != -1) { if (!node.isRoot) { - var refresh = !refreshing && node.status == "loaded"; + var refresh = !refreshing && node.status == "loaded" && Date.now() - node.$lastReadT > 500; watcher.watch(id, refresh); // watch children @@ -424,8 +420,10 @@ define(function(require, exports, module) { }); } - changed = true; - settings.save(); + if (!updateSingleDirectoryChain(true, node)) { + changed = true; + settings.save(); + } } }, plugin); @@ -451,13 +449,29 @@ define(function(require, exports, module) { }); } - changed = true; - settings.save(); + if (!updateSingleDirectoryChain(false, node)) { + changed = true; + settings.save(); + } }, plugin); - - function abortNoStorage() { - if (!c9.has(c9.STORAGE)) - return false; + + function updateSingleDirectoryChain(isExpand, node) { + if (!node.children || node.children.length !== 1) + return; + var child = node.children[0]; + if (!child || !child.isFolder || child.$depth > 0xff) + return; + if (fsCache.isFileHidden(child.path)) + return; + if (isExpand && !child.isOpen) { + expandNode(child); + return true; + } + else if (!isExpand && child.isOpen) { + updateSingleDirectoryChain(false, child); + delete expandedList[child.path]; + return true; + } } // Rename @@ -479,34 +493,58 @@ define(function(require, exports, module) { } var node = e.node; - var name = e.value; + var name = e.value.trim(); // check for a path with the same name, which is not allowed to rename to: var path = node.path; - var newpath = path.replace(/[^\/]+$/, name); + var newpath = join(path, "..", name); // No point in renaming when the name is the same - if (basename(path) == name) + if (path == newpath) return; - - // Returning false from this function will cancel the rename. We do this - // when the name to which the file is to be renamed contains invalid - // characters - if (/[\\\/\n\r]/.test(name)) { - // todo is this still needed? + + var m = /([\0\\\n\r])/.exec(name) || c9.platform == "win32" && /([\\:*?"<>|])/.exec(name); + if (m) { showError( - "Could not rename to '" + ui.htmlentities(name) - + "'. Names can only contain alfanumeric characters, space, . (dot)" - + ", - and _ (underscore). Use the terminal to rename to other names." + "Invalid character '" + m[0] + "' in '" + name + "'" ); return false; } - fs.rename(path, newpath, {}, function(err, success) { }); + // renaming to hidden file can be confusing if one doesn't know about hidden files + if (fsCache.isFileHidden(newpath) && !settings.getBool("user/projecttree/@showhidden")) { + settings.set("user/projecttree/@showhidden", true); + changed = true; + fsCache.showHidden = true; + refresh(true, function(){}); + } - emit("rename", { path: newpath, oldpath: path }); + if (dirname(newpath) != dirname(path)) { + tree.edit.$lastAce && tree.edit.$lastAce.blur(); // TODO this shouldn't be needed when apf focus works + question( + "Confirm move to a new folder", + "move '" + e.oldValue + "' to \n" + + "'" + dirname(newpath) + "'?", + "", + doRename + ); + } else { + doRename(); + } - return false; + function doRename() { + fs.rename(path, newpath, {}, function(err, success) { + if (err) { + var message = err.message; + if (err.code == "EEXIST") + message = "File " + path + " already exists."; + return showError(message); + } + if (dirname(newpath) != dirname(path)) + expandAndSelect(newpath); + }); + emit("rename", { path: newpath, oldpath: path }); + } }, plugin); // Context Menu @@ -977,7 +1015,7 @@ define(function(require, exports, module) { } else { var node = fsCache.findNode(path); - if (node) //Otherwise orphan-append will pick it up + if (node) // Otherwise orphan-append will pick it up expandNode(node); } @@ -1045,14 +1083,14 @@ define(function(require, exports, module) { if (typeof node == "string") node = fsCache.findNode(node, "refresh"); + if (node && !node.isFolder) + node = node.parent; if (node && node.status === "loaded") { tree.provider.setAttribute(node, "status", "pending"); node.children = null; } }); - //c9.dispatchEvent("track_action", { type: "reloadtree" }); - loadProjectTree(false, function(err) { var expandedNodes = Object.keys(expandedList); expandedList = {}; @@ -1067,7 +1105,7 @@ define(function(require, exports, module) { callback(err); tree.provider.on("changeScrollTop", scrollHandler); - emit("refreshComplete") + emit("refreshComplete"); }); } @@ -1100,6 +1138,7 @@ define(function(require, exports, module) { function expandAndSelect(path_or_node) { var node = findNode(path_or_node); expand(node, function(){ + refreshing = false; tree.select(node); scrollToSelection(); }); @@ -1212,10 +1251,12 @@ define(function(require, exports, module) { } function select(path_or_node) { + refreshing = false; tree.select(findNode(path_or_node)); } function selectList(list) { + refreshing = false; tree.selection.setSelection(list.map(function(n) { return findNode(n); })); @@ -1293,6 +1334,9 @@ define(function(require, exports, module) { callback(err, data); }); + var node = fsCache.findNode(newpath, "expand"); + if (node) + expandAndSelect(node); }); } diff --git a/plugins/c9.ide.ui/codebox.js b/plugins/c9.ide.ui/codebox.js index 698e6fca..504a0aab 100644 --- a/plugins/c9.ide.ui/codebox.js +++ b/plugins/c9.ide.ui/codebox.js @@ -62,7 +62,7 @@ apf.codebox = function(struct, tagName) { this.value = ""; this.$draw = function(){ - //Build Main Skin + // Build Main Skin this.$ext = this.$getExternal(); this.$input = this.$getLayoutNode("main", "input", this.$ext); this.$button = this.$getLayoutNode("main", "button", this.$ext); @@ -87,28 +87,28 @@ apf.codebox = function(struct, tagName) { this.$editor = this.ace = ace; ace.renderer.setPadding(2); this.ace.codebox = this; - - ace.on("focus", function() { - dom.removeCssClass(ace.codebox.$ext, "tb_textboxInitial"); - - if (ace.renderer.initialMessageNode) { + + var checkInitial = function() { + var value = ace.getValue(); + if (value && ace.renderer.initialMessageNode) { + dom.removeCssClass(ace.container, "ace_initialMsg"); ace.renderer.scroller.removeChild(ace.renderer.initialMessageNode); ace.renderer.initialMessageNode = null; } - }); + else if (!value && !ace.renderer.initialMessageNode) { + dom.addCssClass(ace.container, "ace_initialMsg"); + var el = document.createElement("div"); + el.className = "tb_textboxInitialMsg"; + el.textContent = ace.codebox["initial-message"] || ""; + ace.renderer.initialMessageNode = el; + ace.renderer.scroller.appendChild(ace.renderer.initialMessageNode); + } + + }; + ace.on("input", checkInitial); - function onBlur() { - if (ace.$isFocused || ace.session.getValue()) - return; - dom.addCssClass(ace.codebox.$ext, "tb_textboxInitial"); - - if (ace.renderer.initialMessageNode) - return; - ace.renderer.initialMessageNode = document.createTextNode(ace.codebox["initial-message"] || ""); - ace.renderer.scroller.appendChild(ace.renderer.initialMessageNode); - } - ace.on("blur", onBlur); - setTimeout(onBlur, 100); + setTimeout(checkInitial, 100); + // todo should we do this here? // ace.on("resize", function(){apf.layout.forceResize();}); @@ -181,6 +181,7 @@ apf.codebox = function(struct, tagName) { new MultiSelect(editor); editor.session.setUndoManager(new UndoManager()); + editor.setOption("indentedSoftWrap", false); editor.setHighlightActiveLine(false); editor.setShowPrintMargin(false); editor.renderer.setShowGutter(false); diff --git a/plugins/c9.ide.ui/forms.js b/plugins/c9.ide.ui/forms.js index a8d916bd..31171f9d 100644 --- a/plugins/c9.ide.ui/forms.js +++ b/plugins/c9.ide.ui/forms.js @@ -481,14 +481,16 @@ define(function(require, exports, module) { } break; default: - if ("value" in item) - el.lastChild.setAttribute('value', item.value); - if ("onclick" in item) - el.lastChild.onclick = item.onclick; - if ("visible" in item) - el.lastChild.setAttribute("visible", item.visible) - if ("zindex" in item) - el.lastChild.setAttribute("zindex", item.zindex) + // supported attributes + var validAttributes = /^(value|visible|zindex|disabled|caption|tooltip|command|class|icon|src|submenu)$/; + Object.keys(item).forEach(function(key) { + // Check for onclick explictly + if (key === "onclick") + return el.onclick = item.onclick; + // Check for attributes we know exist and will directly set + if (validAttributes.test(key)) + return el.setAttribute(key, item[key]); + }); break; } }) diff --git a/plugins/c9.ide.ui/menus.js b/plugins/c9.ide.ui/menus.js index 2db45cc9..0c06c306 100644 --- a/plugins/c9.ide.ui/menus.js +++ b/plugins/c9.ide.ui/menus.js @@ -947,8 +947,10 @@ define(function(require, exports, module) { } function show(x, y, type) { - if (type == "context") - y++; + if (type == "context") { + x += 2; + y += 2; + } lastCoords = { x : x, y : y }; aml.display(x, y); } @@ -967,7 +969,7 @@ define(function(require, exports, module) { checkItems.call(this, e); }, "onitemclick" : function(e) { - emit("itemclick", { value : e.value }); + emit("itemclick", { value : e.value, item: e.relatedNode }); } }); aml.cloud9menu = plugin; diff --git a/plugins/c9.ide.ui/ui.js b/plugins/c9.ide.ui/ui.js index d45b7f5b..9ea5b344 100644 --- a/plugins/c9.ide.ui/ui.js +++ b/plugins/c9.ide.ui/ui.js @@ -124,7 +124,7 @@ define(function(require, module, exports) { "list", "tab", "textbox", "textarea", "radiobutton", "checkbox", "page", "splitter", "hsplitbox", "vsplitbox", "group", "img", "label", "spinner", "dropdown", "BindingColumnRule", "datagrid", "hbox", "vbox", "colorbox", - "frame", "password", "modalwindow", "filler", "splitbutton"].forEach(function(tag) { + "frame", "password", "modalwindow", "filler", "splitbutton", "codebox"].forEach(function(tag) { plugin[tag] = function(struct) { return new apf[tag](struct); }; diff --git a/plugins/c9.ide.ui/widgets.list.js b/plugins/c9.ide.ui/widgets.list.js index 397e4105..928982b9 100644 --- a/plugins/c9.ide.ui/widgets.list.js +++ b/plugins/c9.ide.ui/widgets.list.js @@ -34,7 +34,7 @@ define(function(require, exports, module) { var acetree; var model; var redirectEvents; - var filterRoot; + var fRoot; var meta = {}; var dataType = options.model ? "object" : options.dataType; var excludedEvents = { @@ -251,6 +251,16 @@ define(function(require, exports, module) { */ get enableDragdrop(){ return acetree.getOption("enableDragDrop"); }, set enableDragdrop(value){ acetree.setOption("enableDragDrop", value); }, + /** + * + */ + get enableVariableHeight(){ return model.getItemHeight; }, + set enableVariableHeight(value){ + if (!value) throw new Error("Unable to remove variable height"); + + var variableHeightRowMixin = model.constructor.variableHeightRowMixin; + variableHeightRowMixin.apply(model); + }, /** * */ @@ -296,18 +306,20 @@ define(function(require, exports, module) { set filterKeyword(value){ model.keyword = value; if (!model.keyword) { - filterRoot = null; + fRoot = null; model.reKeyword = null; model.setRoot(model.cachedRoot); } else { model.reKeyword = new RegExp("(" + util.escapeRegExp(model.keyword) + ")", 'i'); - filterRoot = search.treeSearch( - model.cachedRoot.items || model.cachedRoot, + fRoot = search.treeSearch( + model.filterRoot + ? model.filterRoot.items || model.filterRoot + : model.cachedRoot.items || model.cachedRoot, model.keyword, model.filterCaseInsensitive, - null, null, model.indexProperty); - model.setRoot(filterRoot); + null, null, model.filterProperty); + model.setRoot(fRoot); } }, /** @@ -320,6 +332,11 @@ define(function(require, exports, module) { */ get filterProperty(){ return model.filterProperty; }, set filterProperty(value){ model.filterProperty = value; }, + /** + * + */ + get filterRoot(){ return model.filterRoot; }, + set filterRoot(value){ model.filterRoot = value; }, /** * */ @@ -394,11 +411,21 @@ define(function(require, exports, module) { */ get getClassName(){ return model.getClassName; }, set getClassName(fn){ model.getClassName = fn; }, + /** + * + */ + get getTooltipText(){ return model.getTooltipText; }, + set getTooltipText(fn){ model.getTooltipText = fn; }, /** * */ get getIndex(){ return model.getIndex; }, set getIndex(fn){ model.getIndex = fn; }, + /** + * + */ + get getItemHeight(){ return model.getItemHeight; }, + set getItemHeight(fn){ model.getItemHeight = fn; }, // Events _events: [ @@ -494,7 +521,10 @@ define(function(require, exports, module) { */ setRoot: function(root){ model.cachedRoot = root; - return model.setRoot(root); + if (model.keyword) + plugin.filterKeyword = model.keyword; + else + return model.setRoot(root); }, /** * @@ -538,8 +568,8 @@ define(function(require, exports, module) { /** * */ - scrollIntoView: function(anchor, lead, offset){ - return acetree.renderer.scrollCaretIntoView(anchor, lead, offset); + scrollIntoView: function(anchor, offset){ + return acetree.renderer.scrollCaretIntoView(anchor, offset); }, /** * @@ -585,7 +615,10 @@ define(function(require, exports, module) { * */ refresh: function(){ - model.setRoot(filterRoot || plugin.root); + if (model.keyword) + plugin.filterKeyword = model.keyword; + else + model.setRoot(plugin.root); }, /** * diff --git a/plugins/c9.nodeapi/timeago.js b/plugins/c9.nodeapi/timeago.js new file mode 100644 index 00000000..5d7e61f5 --- /dev/null +++ b/plugins/c9.nodeapi/timeago.js @@ -0,0 +1,132 @@ +define(function(require, exports, module) { + + function timeago(timestamp) { + if (timestamp instanceof Date) { + return inWords(timestamp); + } else if (typeof timestamp === "string") { + return inWords(parse(timestamp)); + } else if (typeof timestamp === "number") { + return inWords(new Date(timestamp)); + } else { + return timeagoElement(timestamp); + } + } + + var settings = { + refreshMillis: 60000, + allowFuture: false, + strings: { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "ago", + suffixFromNow: "from now", + seconds: "less than a minute", + minute: "about a minute", + minutes: "%d minutes", + hour: "about an hour", + hours: "about %d hours", + day: "a day", + days: "%d days", + month: "about a month", + months: "%d months", + year: "about a year", + years: "%d years", + wordSeparator: " ", + numbers: [] + } + }; + function distanceInWords(distanceMillis) { + var $l = settings.strings; + var prefix = $l.prefixAgo; + var suffix = $l.suffixAgo; + if (settings.allowFuture) { + if (distanceMillis < 0) { + prefix = $l.prefixFromNow; + suffix = $l.suffixFromNow; + } + } + + var seconds = Math.abs(distanceMillis) / 1000; + var minutes = seconds / 60; + var hours = minutes / 60; + var days = hours / 24; + var years = days / 365; + + function substitute(stringOrFunction, number) { + var string = typeof stringOrFunction === "function" ? stringOrFunction(number, distanceMillis) : stringOrFunction; + var value = ($l.numbers && $l.numbers[number]) || number; + return string.replace(/%d/i, value); + } + + var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || + seconds < 90 && substitute($l.minute, 1) || + minutes < 45 && substitute($l.minutes, Math.round(minutes)) || + minutes < 90 && substitute($l.hour, 1) || + hours < 24 && substitute($l.hours, Math.round(hours)) || + hours < 42 && substitute($l.day, 1) || + days < 30 && substitute($l.days, Math.round(days)) || + days < 45 && substitute($l.month, 1) || + days < 365 && substitute($l.months, Math.round(days / 30)) || + years < 1.5 && substitute($l.year, 1) || + substitute($l.years, Math.round(years)); + + var separator = $l.wordSeparator || ""; + if ($l.wordSeparator === undefined) { separator = " "; } + return [prefix, words, suffix].filter(function(s) { return s; }) + .map(function(s){ return s.trim(); }) + .join(separator); + } + function parse(iso8601) { + var s = iso8601.trim(); + s = s.replace(/\.\d+/,""); // remove milliseconds + s = s.replace(/-/,"/").replace(/-/,"/"); + s = s.replace(/T/," ").replace(/Z/," UTC"); + s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 + return new Date(s); + } + function datetime(elem) { + var iso8601 = isTime(elem) ? elem.getAttribute("datetime") : elem.getAttribute("title"); + return parse(iso8601); + } + function isTime(elem) { + return elem.tagName.toLowerCase() === "time"; + } + + function timeagoElement(elem) { + refresh(elem); + if (settings.refreshMillis > 0) + setInterval(refresh.bind(null, elem), settings.refreshMillis); + } + + function refresh(elem) { + var datetime = prepareDateTime(elem); + if (!isNaN(datetime)) + elem.textContent = inWords(datetime); + } + + function prepareDateTime(elem) { + var timeagoAttr = elem.getAttribute("timeago"); + if (timeagoAttr) + return new Date(timeagoAttr); + var data = datetime(elem); + elem.setAttribute("timeago", data); + var text = elem.textContent.trim(); + if (text.length > 0 && !(isTime(elem) && elem.title)) + elem.title = text; + return data; + } + + function inWords(date) { + return distanceInWords(distance(date)); + } + + function distance(date) { + return (new Date().getTime() - date.getTime()); + } + + // fix for IE6 suckage + document.createElement("abbr"); + document.createElement("time"); + + module.exports = timeago; +}); \ No newline at end of file diff --git a/plugins/c9.static/build.js b/plugins/c9.static/build.js index ea929cda..b8e69593 100644 --- a/plugins/c9.static/build.js +++ b/plugins/c9.static/build.js @@ -254,6 +254,7 @@ function main(options, imports, register) { "plugins/c9.ide.language/worker", "plugins/c9.ide.language.generic/local_completer", "plugins/c9.ide.language.generic/snippet_completer", + "plugins/c9.ide.language.generic/mode_completer", "plugins/c9.ide.language.generic/open_files_local_completer", "plugins/c9.ide.language.generic/simple/make", "plugins/c9.ide.language.generic/simple/shell", diff --git a/plugins/c9.static/cdn.js b/plugins/c9.static/cdn.js index ede7d5f6..023a2257 100644 --- a/plugins/c9.static/cdn.js +++ b/plugins/c9.static/cdn.js @@ -76,6 +76,8 @@ function main(options, imports, register) { var parts = e.split(" "); var id = parts[1]; var etag = parts[0]; + if (!id || /^https?:\/\//.test(id)) + return q.oneDone(); var path = resolveModulePath(id, req.pathConfig.pathMap); if (path == id && !/^(\/|\w:)/.test(path)) { diff --git a/plugins/c9.vfs.client/endpoint.js b/plugins/c9.vfs.client/endpoint.js index c90ff5e3..e36d2fda 100644 --- a/plugins/c9.vfs.client/endpoint.js +++ b/plugins/c9.vfs.client/endpoint.js @@ -225,6 +225,10 @@ define(function(require, exports, module) { callback(fatalError(res.error.message, "dashboard")); return; } + else if (err.code == 404) { + callback(fatalError("This workspace no longer appears to exist or failed to be created.", "dashboard")); + return; + } else if (err.code === 428 && res.error) { emit("restore", { projectState: res.error.projectState, @@ -251,6 +255,13 @@ define(function(require, exports, module) { }, 10000); return; } + else if (err.code == 503) { + // service unavailable + setTimeout(function() { + tryNext(i); + }, res.error.retryIn || 15000); + return; + } else if (err.code === 500 && res && res.error && res.error.cause) { return callback(res.error.cause.message); } diff --git a/plugins/c9.vfs.client/vfs_client.js b/plugins/c9.vfs.client/vfs_client.js index 9e11a4d0..0f07d9b4 100644 --- a/plugins/c9.vfs.client/vfs_client.js +++ b/plugins/c9.vfs.client/vfs_client.js @@ -253,7 +253,7 @@ define(function(require, exports, module) { err.message = "SSH permission denied. Please review your workspace configuration."; return showAlert("Workspace Error", "Unable to access your workspace", err.message, function() { window.location = dashboardUrl; - }); + }, { yes: "Return to dashboard" }); case "reload": lastError = showError(err.message + ". Please reload this window.", -1); setTimeout(function() { diff --git a/plugins/c9.vfs.standalone/www/test.js b/plugins/c9.vfs.standalone/www/test.js index f30a80b4..3d2217ef 100644 --- a/plugins/c9.vfs.standalone/www/test.js +++ b/plugins/c9.vfs.standalone/www/test.js @@ -199,6 +199,8 @@ require([ c.exec = function(name) { commands[name].exec(); }; + c.getPrettyHotkey = function(name) { return "" }; + c.getHotkey = function(name) { return "" }; c.getExceptionList = function(){ return []; }; return c; diff --git a/scripts/install-sdk.sh b/scripts/install-sdk.sh index 38448df0..a953cad5 100755 --- a/scripts/install-sdk.sh +++ b/scripts/install-sdk.sh @@ -96,7 +96,7 @@ updateNodeModules() { echo "${magenta}--- Running npm install --------------------------------------------${resetColor}" safeInstall(){ deps=(`"$NODE" -e 'console.log(Object.keys(require("./package.json").dependencies).join(" "))'`) - for m in $deps; do echo $m; + for m in ${deps[@]}; do echo $m; "$NPM" install --loglevel warn $m || true done } diff --git a/scripts/makestatic.js b/scripts/makestatic.js index 92238a73..7290f44c 100755 --- a/scripts/makestatic.js +++ b/scripts/makestatic.js @@ -85,7 +85,8 @@ function main(config, settings, options, callback) { findAllAndPurge: function(maxVfsAge, callback) { callback(null, [{}]); } - } + }, + "User": {} }, "redis": {}, "health": {