diff --git a/configs/client-config_test.js b/configs/client-config_test.js index c26942dd..45f74285 100755 --- a/configs/client-config_test.js +++ b/configs/client-config_test.js @@ -99,7 +99,7 @@ describe("client config consistency", function() { } }); - var provides = {"auth.bootstrap": 1, "hub": 1}; + var provides = { "auth.bootstrap": 1, "hub": 1, "app": 1 }; var paths = {}; clientPlugins.forEach(function(p) { if (paths[p.packagePath]) { diff --git a/configs/ide/default.js b/configs/ide/default.js index 14c381eb..cb53d374 100644 --- a/configs/ide/default.js +++ b/configs/ide/default.js @@ -19,6 +19,7 @@ module.exports = function(options) { options.workspaceDir = normalize(options.workspaceDir); options.installPath = normalize(options.installPath); options.home = normalize(options.home); + options.sourceDir = options.sourceDir && normalize(options.sourceDir); var workspaceDir = options.workspaceDir; var debug = options.debug !== undefined ? options.debug : false; @@ -63,7 +64,8 @@ module.exports = function(options) { projectName: options.projectName || "Project", configName: options.configName, standalone: options.standalone, - dashboardUrl: options.dashboardUrl + dashboardUrl: options.dashboardUrl, + sourceDir: options.sourceDir, }, { packagePath: "plugins/c9.core/settings", @@ -81,30 +83,16 @@ module.exports = function(options) { // baseUrl: options.apiUrl // }, "plugins/c9.core/util", - { - packagePath: "plugins/c9.ide.plugins/loader", - plugins: options.plugins || [], - loadFromDisk: options.standalone - }, - { - packagePath: "plugins/c9.ide.plugins/installer", - updates: options.pluginUpdates || [] - }, { packagePath: "plugins/c9.ide.plugins/manager", staticPrefix: staticPrefix + "/plugins/c9.ide.plugins", devel: devel }, - { - packagePath: "plugins/c9.ide.plugins/debug" - }, - { - packagePath: "plugins/c9.ide.plugins/packages" - }, - { - packagePath: "plugins/c9.ide.plugins/test", - staticPrefix: staticPrefix + "/plugins/c9.ide.plugins" - }, + "plugins/c9.ide.plugins/gui", + // { + // packagePath: "plugins/c9.ide.plugins/test", + // staticPrefix: staticPrefix + "/plugins/c9.ide.plugins" + // }, // VFS "plugins/c9.vfs.client/vfs.ping", diff --git a/configs/standalone.js b/configs/standalone.js index 31b33d68..df9358f1 100644 --- a/configs/standalone.js +++ b/configs/standalone.js @@ -78,6 +78,9 @@ module.exports = function(config, optimist) { if (argv.hosted) config.client_config = "default-hosted"; + if (!argv.hosted) + config.sourceDir = path.dirname(__dirname); + config.workspaceDir = baseProc; config.settingDir = argv["setting-path"]; config.projectName = path.basename(baseProc); @@ -88,7 +91,7 @@ module.exports = function(config, optimist) { config.startBridge = startBridge; if (testing && argv.k) - require("child_process").exec("tmux -L cloud91.9 kill-server", function(){}); + require("child_process").exec("tmux -L cloud91.9 kill-server", function() {}); var isLocalhost = host == "localhost" || host == "127.0.0.1"; if (!/:/.test(argv.auth) && !isLocalhost) { diff --git a/node_modules/ace_tree/lib/ace_tree/mouse/default_handlers.js b/node_modules/ace_tree/lib/ace_tree/mouse/default_handlers.js index b568a458..54247f61 100644 --- a/node_modules/ace_tree/lib/ace_tree/mouse/default_handlers.js +++ b/node_modules/ace_tree/lib/ace_tree/mouse/default_handlers.js @@ -137,13 +137,15 @@ function DefaultHandlers(mouseHandler) { } this.$clickNode = null; } else if (dom.hasCssClass(target, "checkbox")) { + var nodes = inSelection && editor.selection.getSelectedNodes(); + provider._signal("toggleCheckbox", { target: node, selectedNodes: nodes }); + // consider deprecating this node.isChecked = !node.isChecked; - if (inSelection) { - var nodes = editor.selection.getSelectedNodes(); - nodes.forEach(function(n){ n.isChecked = node.isChecked }); + if (nodes) { + nodes.forEach(function(n) { n.isChecked = node.isChecked }); } - provider._signal(node.isChecked ? "check" : "uncheck", inSelection ? nodes : [node]); - provider._signal("change") + provider._signal(node.isChecked ? "check" : "uncheck", nodes || [node]); + provider._signal("change"); } else if (dom.hasCssClass(target, "icon-ok")) { if (ev.getShiftKey()) { editor.selection.expandSelection(node, null, true); diff --git a/node_modules/architect/architect.js b/node_modules/architect/architect.js index ef5b4edd..5b9c188d 100644 --- a/node_modules/architect/architect.js +++ b/node_modules/architect/architect.js @@ -20,10 +20,10 @@ if (typeof module === "object") (function () { // This is assumed to be used at startup and uses sync I/O as well as can // throw exceptions. It loads and parses a config file. function loadConfig(configPath, callback) { - var config = require(configPath); - var base = dirname(configPath); + var config = require(configPath); + var base = dirname(configPath); - return resolveConfig(config, base, callback); + return resolveConfig(config, base, callback); } function resolveConfig(config, base, callback) { @@ -126,15 +126,21 @@ else (function () { // Mass-Load path-based plugins using amd's require require(paths, function () { var args = arguments; + var err = []; paths.forEach(function (name, i) { var module = args[i]; var plugin = config[pluginIndexes[name]]; + if (!module || !plugin) return err.push(name); plugin.setup = module; plugin.provides = module.provides || []; plugin.consumes = module.consumes || []; }); + if (err.length) + return callback(new Error("Missing plugins: " + err)); callback(null, config); - }, errback); + }, errback || function(err) { + callback(err); + }); } }()); @@ -173,7 +179,8 @@ function checkCycles(config, lookup) { }); var resolved = { - hub: true + hub: true, + app: true }; var changed = true; var sorted = []; @@ -209,7 +216,6 @@ function checkCycles(config, lookup) { if (plugins.length) { var unresolved = {}; plugins.forEach(function(plugin) { - delete plugin.config; plugin.consumes.forEach(function(name) { if (unresolved[name] === false) return; @@ -244,16 +250,12 @@ function checkCycles(config, lookup) { function Architect(config) { var app = this; app.config = config; - app.packages = {}; - app.pluginToPackage = {}; + app.serviceToPlugin = {}; var isAdditionalMode; var services = app.services = { - hub: { - on: function (name, callback) { - app.on(name, callback); - } - } + hub: app, + app: app }; // Check the config @@ -275,10 +277,6 @@ function Architect(config) { }); } - var m = /^plugins\/([^\/]+)|\/plugins\/[^\/]+\/([^\/]+)/.exec(plugin.packagePath); - var packageName = m && (m[1] || m[2]); - if (!app.packages[packageName]) app.packages[packageName] = []; - if (DEBUG) { recur++; plugin.setup(plugin, imports, register); @@ -315,13 +313,8 @@ function Architect(config) { return app.emit("error", err); } services[name] = provided[name]; - app.pluginToPackage[name] = { - path: plugin.packagePath, - package: packageName, - version: plugin.version, - isAdditionalMode: isAdditionalMode - }; - app.packages[packageName].push(name); + app.serviceToPlugin[name] = plugin; + plugin.__isAdditionalMode = isAdditionalMode; app.emit("service", name, services[name], plugin); }); @@ -338,18 +331,18 @@ function Architect(config) { // Give createApp some time to subscribe to our "ready" event (typeof process === "object" ? process.nextTick : setTimeout)(startPlugins); - this.loadAdditionalPlugins = function(additionalConfig, callback){ + this.loadAdditionalPlugins = function(additionalConfig, callback) { isAdditionalMode = true; exports.resolveConfig(additionalConfig, function (err, additionalConfig) { if (err) return callback(err); - app.once(ready ? "ready-additional" : "ready", function(app){ + app.once(ready ? "ready-additional" : "ready", function(app) { callback(null, app); }); // What about error state? // Check the config - hopefully this works - var _sortedPlugins = checkConfig(additionalConfig, function(name){ + var _sortedPlugins = checkConfig(additionalConfig, function(name) { return services[name]; }); @@ -359,7 +352,7 @@ function Architect(config) { startPlugins(true); } else { - _sortedPlugins.forEach(function(item){ + _sortedPlugins.forEach(function(item) { sortedPlugins.push(item); }); } @@ -394,7 +387,7 @@ function createApp(config, callback) { var app; try { app = new Architect(config); - } catch(err) { + } catch (err) { if (!callback) throw err; return callback(err, app); } @@ -416,8 +409,6 @@ function createApp(config, callback) { app.removeListener("ready", onReady); callback(err, app); } - - return app; } return exports; diff --git a/plugins/c9.cli.bridge/bridge.js b/plugins/c9.cli.bridge/bridge.js index 8589f630..e291c098 100644 --- a/plugins/c9.cli.bridge/bridge.js +++ b/plugins/c9.cli.bridge/bridge.js @@ -81,7 +81,8 @@ define(function(require, exports, module) { /***** Methods *****/ plugin.on("load", function() { - c9.on("connect", load, plugin); + if (c9.connected) load(); + else c9.on("connect", load, plugin); }); plugin.on("unload", function() { diff --git a/plugins/c9.cli.publish/publish.js b/plugins/c9.cli.publish/publish.js index 5043c490..2c5be13b 100644 --- a/plugins/c9.cli.publish/publish.js +++ b/plugins/c9.cli.publish/publish.js @@ -196,9 +196,9 @@ define(function(require, exports, module) { // Write the package.json file var indent = data.match(/{\n\r?^ {4}"/) ? 4 : 2; var newData = JSON.stringify(json, null, indent); - fs.writeFile(cwd + "/.build/package.json", newData, function() { + fs.writeFile(cwd + "/c9build/package.json", newData, function() { if (dryRun) - return next(); // if dry-run is passed only update path in .build + return next(); // if dry-run is passed only update path in c9build fs.writeFile(packagePath, newData, function(err) { if (err) return callback(err); return next(); @@ -230,97 +230,59 @@ define(function(require, exports, module) { } } - if (files.indexOf("builders") != -1) { - forEachFile(cwd + "/builders", function(filename, data) { - packedFiles.push(cwd + "/builders/" + filename); + function parseHeader(data, filename) { + var firstLine = data.split("\n", 1)[0].replace(/\/\*|\*\//g, "").trim(); + var info = {}; + firstLine.split(";").forEach(function(n) { + var key = n.split(":"); + if (key.length != 2) + return console.error("Ignoring invalid key " + n + " in " + filename); + info[key[0].trim()] = key[1].trim(); + }); + info.data = firstLine; + return info; + } + + function addResource(type) { + forEachFile(cwd + "/" + type, function(filename, data) { + packedFiles.push(cwd + "/"+ type + "/" + filename); extraCode.push({ - type: "builders", + type: type, filename: filename, data: data }); }); } - if (files.indexOf("keymaps") != -1) { - forEachFile(cwd + "/keymaps", function(filename, data) { - packedFiles.push(cwd + "/keymaps/" + filename); - extraCode.push({ - type: "keymaps", - filename: filename, - data: data - }); - }); - } - if (files.indexOf("modes") != -1) { - forEachFile(cwd + "/modes", function(filename, data) { + function addMode(type) { + forEachFile(cwd + "/modes", function(filename, data) { if (/(?:_highlight_rules|_test|_worker|_fold|_behaviou?r)\.js$/.test(filename)) return; if (!/\.js$/.test(filename)) return; - var firstLine = data.split("\n", 1)[0].replace(/\/\*|\*\//g, "").trim(); + var info = parseHeader(data, cwd + "/modes/" + filename); - if (!/caption\s*:[^;]+/i.test(firstLine)) { - packedFiles.push(cwd + "/modes/" + filename); - console.error("Ignoring mode with invalid header: ", firstLine); - console.error(" at " + cwd + "/modes/" + filename); - return; - } - extraCode.push({ - type: "modes", - filename: filename, - data: firstLine - }); - }); - } - if (files.indexOf("outline") != -1) { - forEachFile(cwd + "/outline", function(filename, data) { - packedFiles.push(cwd + "/outline/" + filename); - extraCode.push({ - type: "outline", - filename: filename, - data: data - }); - }); - } - if (files.indexOf("runners") != -1) { - forEachFile(cwd + "/runners", function(filename, data) { - packedFiles.push(cwd + "/runners/" + filename); - extraCode.push({ - type: "runners", - filename: filename, - data: data - }); - }); - } - if (files.indexOf("snippets") != -1) { - forEachFile(cwd + "/snippets", function(filename, data) { - packedFiles.push(cwd + "/snippets/" + filename); - extraCode.push({ - type: "snippets", - filename: filename, - data: data - }); - }); - } - if (files.indexOf("themes") != -1) { - forEachFile(cwd + "/themes", function(filename, data) { - packedFiles.push(cwd + "/themes/" + filename); - extraCode.push({ - type: "themes", - filename: filename, - data: data - }); - }); - } - if (files.indexOf("templates") != -1) { - forEachFile(cwd + "/templates", function(filename, data) { - packedFiles.push(cwd + "/templates/" + filename); - extraCode.push({ - type: "templates", - filename: filename, - data: data - }); + if (!info.caption) info.caption = filename; + + info.type = "modes"; + info.filename = filename; + extraCode.push(info); }); } + var handlers = { + templates: addResource, + snippets: addResource, + builders: addResource, + keymaps: addResource, + outline: addResource, + runners: addResource, + themes: addResource, + modes: addMode, + }; + files.forEach(function(type) { + if (handlers.hasOwnProperty(type)) + handlers[type](type); + }); + packedFiles.push(cwd + "/package." + packageName + ".js"); @@ -348,7 +310,7 @@ define(function(require, exports, module) { main.consumes = [ "Plugin", "plugin.debug" ]; - main.provides = []; + main.provides = ["PACKAGE_NAME.bundle"]; return main; function main(options, imports, register) { var debug = imports["plugin.debug"]; @@ -363,7 +325,7 @@ define(function(require, exports, module) { plugin.load("PACKAGE_NAME.bundle"); - register(null, {}); + register(null, {"PACKAGE_NAME.bundle": plugin}); } }); }).toString(); @@ -382,7 +344,7 @@ define(function(require, exports, module) { staticPlugin = { source: code, - id: "plugins/" + packageName + "/__static__", + id: "plugins/" + packageName + "/" + packageName + ".bundle", path: "" }; next(); @@ -448,17 +410,17 @@ define(function(require, exports, module) { }, function(next) { proc.execFile("rm", { - args: ["-rf", ".c9/.build"], + args: ["-rf", "c9build"], cwd: cwd }, function() { - mkdirP(cwd + "/.build"); - fs.writeFile(cwd + "/.build/package." + packageName + ".js", result.code, "utf8", next); + mkdirP(cwd + "/c9build"); + fs.writeFile(cwd + "/c9build/package." + packageName + ".js", result.code, "utf8", next); }); }, function(next) { var copy = require("architect-build/copy"); - var excludeRe = /^\.(\w*ignore|git|c9|hg|build)$/; + var excludeRe = /^\.(\w*ignore|git|c9|hg|build)$|^(c9)?build$|_test\.js$/; var excludeMap = Object.create(null); packedFiles.push(cwd + "/package." + packageName + ".js"); @@ -470,7 +432,7 @@ define(function(require, exports, module) { if (json.installer) excludeMap["/" + normalizePath(Path.relative(cwd, json.installer))] = 0; - copy(cwd, cwd + "/.build", { + copy(cwd, cwd + "/c9build", { exclude: function(name, parent) { if (excludeRe.test(name)) return true; diff --git a/plugins/c9.core/c9.js b/plugins/c9.core/c9.js index fe8c1597..0de92bf4 100644 --- a/plugins/c9.core/c9.js +++ b/plugins/c9.core/c9.js @@ -6,7 +6,7 @@ * @main c9.core */ define(function(require, module, exports) { - main.consumes = ["Plugin", "ext", "vfs"]; + main.consumes = ["Plugin", "vfs"]; main.provides = ["c9"]; return main; @@ -20,8 +20,6 @@ define(function(require, module, exports) { var emit = plugin.getEmitter(); emit.setMaxListeners(500); - imports.ext.vfs = imports.vfs; - var loaded = false; var loggedIn = false; var isReady = false; diff --git a/plugins/c9.core/ext.js b/plugins/c9.core/ext.js index d1ec89ae..8e07a19f 100644 --- a/plugins/c9.core/ext.js +++ b/plugins/c9.core/ext.js @@ -1,10 +1,12 @@ define(function(require, exports, module) { - main.consumes = []; + main.consumes = ["app"]; main.provides = ["ext", "Plugin"]; return main; function main(options, imports, register) { var Emitter = require("events").EventEmitter; + var architectApp = imports.app; + var plugins = []; var lut = {}; @@ -18,12 +20,7 @@ define(function(require, exports, module) { var plugin = new Plugin("Ajax.org", main.consumes); var emit = plugin.getEmitter(); - var vfs, settings, api; - - plugin.__defineSetter__("vfs", function(remote) { - vfs = remote; - delete plugin.vfs; - }); + var settings, api; plugin.__defineSetter__("settings", function(remote) { settings = remote; @@ -80,17 +77,8 @@ define(function(require, exports, module) { if (!plugin.registered) return; - if (!ignoreDeps && getDependencies(plugin.name).length) { - //@todo this should be moved to whoever is calling this. - // if (!silent) - // util.alert( - // "Could not disable extension", - // "Extension is still in use", - // "This extension cannot be disabled, because it is still in use by the following plugins:

" - // + " - " + usedBy.join("
- ") - // + "

Please disable those plugins first."); + if (!ignoreDeps && getDependents(plugin.name).length) return false; - } if (!keep) plugins.splice(plugins.indexOf(plugin), 1); @@ -108,7 +96,7 @@ define(function(require, exports, module) { emit("unregister", { plugin: plugin }); } - function getDependencies(pluginName) { + function getDependents(pluginName) { var usedBy = []; // Check for dependencies needing this plugin @@ -155,12 +143,14 @@ define(function(require, exports, module) { } function loadRemotePlugin(id, options, callback) { + var vfs = architectApp.services.vfs; vfs.extend(id, options, function(err, meta) { callback(err, meta && meta.api); }); } function fetchRemoteApi(id, callback) { + var vfs = architectApp.services.vfs; vfs.use(id, {}, function(err, meta) { callback(err, meta && meta.api); }); @@ -284,7 +274,7 @@ define(function(require, exports, module) { /** * */ - getDependencies: getDependencies, + getDependents: getDependents, /** * diff --git a/plugins/c9.core/util.js b/plugins/c9.core/util.js index d0e01c03..da1fba99 100644 --- a/plugins/c9.core/util.js +++ b/plugins/c9.core/util.js @@ -153,7 +153,7 @@ define(function(require, exports, module) { var data = strJson.replace(/(^|\n)\s*\/\/.*/g, ""); try { return JSON.parse(data); } - catch (e) { cb(e); return false; } + catch (e) { cb && cb(e); return false; } }; /** diff --git a/plugins/c9.ide.behaviors/tabs.js b/plugins/c9.ide.behaviors/tabs.js index dc108154..474e5025 100644 --- a/plugins/c9.ide.behaviors/tabs.js +++ b/plugins/c9.ide.behaviors/tabs.js @@ -38,7 +38,7 @@ define(function(require, exports, module) { var cycleKeyPressed, changedTabs, unchangedTabs, dirtyNextTab, dirtyNextPane; var ACTIVEPAGE = function() { return tabs.focussedTab; }; - var ACTIVEPATH = function() { var tab = tabs.focussedTab; return tab && (tab.path || tab.relatedPath || tab.editor.getPathAsync); }; + var ACTIVEPATH = function() { var tab = mnuContext.$tab || tabs.focussedTab; return tab && (tab.path || tab.relatedPath || tab.editor.getPathAsync); }; var MORETABS = function() { return tabs.getTabs().length > 1; }; var MORETABSINPANE = function() { return tabs.focussedTab && tabs.focussedTab.pane.getTabs().length > 1; }; var MOREPANES = function() { return tabs.getPanes().length > 1; }; diff --git a/plugins/c9.ide.collab/workspace.js b/plugins/c9.ide.collab/workspace.js index d290e08a..ea67e4e7 100644 --- a/plugins/c9.ide.collab/workspace.js +++ b/plugins/c9.ide.collab/workspace.js @@ -380,9 +380,9 @@ define(function(require, exports, module) { get myUserId() { return myUserId; }, /** * Specifies wether the collab workspace was previously loaded and collab was connected - or not - * @property {Boolean} loaded + * @property {Boolean} isReady */ - get loaded() { return loadedWorkspace; }, + get isReady() { return loadedWorkspace; }, /** * Gets my filesystem access to this workspace: * Values can be either "r" or "rw" diff --git a/plugins/c9.ide.configuration/configure.js b/plugins/c9.ide.configuration/configure.js index ea5f1938..2b066a5c 100644 --- a/plugins/c9.ide.configuration/configure.js +++ b/plugins/c9.ide.configuration/configure.js @@ -3,7 +3,7 @@ define(function(require, exports, module) { "Plugin", "dialog.error", "ui", "settings", "tabManager", "save", "menus", "preferences.keybindings", "preferences.general", "preferences.project", "c9", "commands", "watcher", "fs", - "tree.favorites", "preferences", "util" + "tree.favorites", "util", "app" ]; main.provides = ["configure"]; return main; @@ -16,13 +16,13 @@ define(function(require, exports, module) { var menus = imports.menus; var watcher = imports.watcher; var tabManager = imports.tabManager; - var preferences = imports.preferences; var ui = imports.ui; var c9 = imports.c9; var fs = imports.fs; var kbprefs = imports["preferences.keybindings"]; var genprefs = imports["preferences.general"]; var prjprefs = imports["preferences.project"]; + var services = imports.app.services; var showError = imports["dialog.error"].show; var favs = imports["tree.favorites"]; var util = imports.util; @@ -35,7 +35,7 @@ define(function(require, exports, module) { // var emit = plugin.getEmitter(); var cssSession = new Plugin("Ajax.org", main.consumes); - var services, initPlugin; + var initPlugin; var pathFromFavorite = options.pathFromFavorite; @@ -288,24 +288,27 @@ define(function(require, exports, module) { var script = settings.get("user/config/init.js") || ""; openTab("~/.c9/init.js", script, "javascript", "// You can access plugins via the 'services' global variable\n" + - "/*global services, plugin*/\n"); + "/*global services, plugin*/\n" + + "\n" + + "// to load plugins use\n" + + "// services.pluginManager.loadPackage([\n" + + "// \"https://.github.io//build/package..js\",\n" + + "// \"~/.c9/plugins//package.json\",\n" + + "// ]);\n"); } function editStylesCss() { - // preferences.hide(); var css = settings.get("user/config/styles.css") || ""; openTab("~/.c9/styles.css", css, "css"); } function editProjectSettings() { - // preferences.hide(); var value = JSON.stringify(settings.model.project, 0, " ") .replace(/"true"/g, "true") .replace(/"false"/g, "false"); openTab(settings.paths.project, value, "javascript"); } function editUserSettings() { - // preferences.hide(); var value = JSON.stringify(settings.model.user, 0, " ") .replace(/"true"/g, "true") .replace(/"false"/g, "false"); @@ -353,9 +356,6 @@ define(function(require, exports, module) { * **/ plugin.freezePublicAPI({ - get services() { return services; }, - set services(value) { services = value; }, - /** * */ diff --git a/plugins/c9.ide.editors/tab.js b/plugins/c9.ide.editors/tab.js index afed56c3..732b65e5 100644 --- a/plugins/c9.ide.editors/tab.js +++ b/plugins/c9.ide.editors/tab.js @@ -1,5 +1,5 @@ define(function(require, module, exports) { - main.consumes = ["Plugin", "ui", "Document", "dialog.alert", "settings"]; + main.consumes = ["Plugin", "ui", "Document", "dialog.alert"]; main.provides = ["Tab"]; return main; diff --git a/plugins/c9.ide.find.infiles/findinfiles.js b/plugins/c9.ide.find.infiles/findinfiles.js index 4ddebe1c..cb1e867b 100644 --- a/plugins/c9.ide.find.infiles/findinfiles.js +++ b/plugins/c9.ide.find.infiles/findinfiles.js @@ -1,6 +1,6 @@ define(function(require, exports, module) { main.consumes = [ - "Plugin", "c9", "util", "settings", "ui", "layout", "findreplace", + "Plugin", "c9", "util", "settings", "ui", "layout", "find", "anims", "menus", "tabManager", "commands", "tooltip", "tree", "apf", "console", "preferences", "dialog.question", "tree.favorites", "save" @@ -27,6 +27,7 @@ define(function(require, exports, module) { var find = imports.find; var save = imports.save; var question = imports["dialog.question"].show; + var apf = imports.apf; var markup = require("text!./findinfiles.xml"); var lib = require("plugins/c9.ide.find.replace/libsearch"); diff --git a/plugins/c9.ide.plugins/debug.js b/plugins/c9.ide.plugins/debug.js deleted file mode 100644 index 91e150da..00000000 --- a/plugins/c9.ide.plugins/debug.js +++ /dev/null @@ -1,554 +0,0 @@ -/*global requirejs*/ -define(function(require, exports, module) { - main.consumes = [ - "Plugin", "vfs", "fs", "plugin.loader", "c9", "ext", "watcher", - "dialog.notification", "dialog.info", "ui", "menus", "commands", "settings", "auth", - "installer", "find", "util", "preferences.experimental" - ]; - main.provides = ["plugin.debug"]; - return main; - - function main(options, imports, register) { - var Plugin = imports.Plugin; - var vfs = imports.vfs; - var watcher = imports.watcher; - var ext = imports.ext; - var util = imports.util; - var find = imports.find; - var ui = imports.ui; - var menus = imports.menus; - var installer = imports.installer; - var settings = imports.settings; - var commands = imports.commands; - var fs = imports.fs; - var c9 = imports.c9; - var auth = imports.auth; - var loader = imports["plugin.loader"]; - var notify = imports["dialog.notification"].show; - var experimental = imports["preferences.experimental"]; - var showInfo = imports["dialog.info"].show; - - var dirname = require("path").dirname; - var basename = require("path").basename; - var join = require("path").join; - var async = require("async"); - - var architect; - - /***** Initialization *****/ - - var plugin = new Plugin("Ajax.org", main.consumes); - var emit = plugin.getEmitter(); - - var plugins = []; - - var ENABLED = c9.location.indexOf("debug=2") > -1; - var HASSDK = ENABLED || experimental.addExperiment("sdk", false, "SDK/Load Custom Plugins"); - - var reParts = /^(builders|keymaps|modes|outline|runners|snippets|themes|templates)\/(.*)/; - var reModule = /(?:_highlight_rules|_test|_worker|_fold|_behaviou?r)\.js$/; - var jsExtRe = /\.js$/; - - var loaded = false; - function load() { - if (loaded) return false; - loaded = true; - - if (!HASSDK) return; - - menus.addItemByPath("Tools/~", new ui.divider(), 100000, plugin); - menus.addItemByPath("Tools/Developer", null, 100100, plugin); - - if (!ENABLED) { - menus.addItemByPath("Tools/Developer/Start in Debug Mode", new ui.item({ - onclick: function() { - var url = location.href + (location.href.indexOf("?") > -1 - ? "&debug=2" - : "?debug=2"); - window.open(url); - } - }), 900, plugin); - } - - if (!ENABLED) return; - - notify("
You are in Debug Mode. " - + "Don't forget to open the browser's dev tools to see any errors.", - false); - - // Insert relevant LESS libraries - var theme = settings.get("user/general/@skin"); - - ui.defineLessLibrary(require("text!../c9.ide.layout.classic/themes/default-" + theme + ".less"), plugin); - ui.defineLessLibrary(require("text!../c9.ide.layout.classic/less/lesshat.less"), plugin); - - fs.readdir("~/.c9/plugins", function(err, list) { - if (err) return console.error(err); - - var names = loader.plugins; - var toLoad = []; - - list.forEach(function(stat) { - var name = stat.name; - // If the plugin doesn't exist - if (names.indexOf(name) == -1 && name.charAt(0) != "." && name.charAt(0) != "_") - toLoad.push(name); - }); - - loadPlugins(toLoad); - }); - - commands.addCommand({ - name: "reloadCustomPlugin", - group: "Plugins", - bindKey: { - mac: "Command-Enter", - win: "Ctrl-Enter" - }, - exec: function() { - reloadPluginUI(); - } - }, plugin); - - menus.addItemByPath("Tools/Developer/Reload Custom Plugin", new ui.item({ - command: "reloadCustomPlugin" - }), 1000, plugin); - } - - /***** Methods *****/ - - function loadPlugins(list) { - if (!vfs.connected) { - vfs.once("connect", loadPlugins.bind(this, config)); - return; - } - - if (typeof list == "string") - list = [list]; - - var config = []; - var loadConfig = function() { - architect.loadAdditionalPlugins(config, function(err) { - if (err) console.error(err); - }); - }; - - async.each(list, function(name, next) { - var resourceHolder = new Plugin(); - var resourceVersion = ""; - - resourceHolder.on("load", function() { - if (inited) load(); - }); - - resourceHolder.freezePublicAPI({ - get version() { return resourceVersion; }, - set version(v) { resourceVersion = v; } - }); - - var inited = false; - function load() { - async.parallel([ - function(next) { - // Fetch package.json - fs.readFile("~/.c9/plugins/" + name + "/package.json", function(err, data) { - if (err) - return next(err); - - try { - var options = JSON.parse(data); - if (!options.plugins) - throw new Error("Missing plugins property in package.json of " + name); - } - catch (e) { - return next(err); - } - - var host = vfs.baseUrl + "/"; - var base = join(String(c9.projectId), - "plugins", auth.accessToken); - - // Configure Require.js - var pathConfig = {}; - pathConfig["plugins/" + name] = host + join(base, name); - requirejs.config({ paths: pathConfig }); - - // Add the plugin to the config - Object.keys(options.plugins).forEach(function(path) { - var pluginPath = name + "/" + path; - - // Watch project path - watch("~/.c9/plugins/" + pluginPath); - var cfg = options.plugins[path]; - cfg.packagePath = "plugins/" + name + "/" + path; - cfg.staticPrefix = host + join(base, name); - cfg.apikey = "0000000000000000000000000000="; - - // Set version for package manager - cfg.version = options.version; - - config.push(cfg); - plugins.push(name + "/" + path); - }); - - // Set version for package manager - resourceHolder.version = options.version; - - // Start the installer if one is included - if (options.installer) { - addStaticPlugin("installer", name, options.installer, - null, resourceHolder); - } - - next(); - }); - }, - function(next) { - var path = join("~/.c9/plugins", name); - var rePath = new RegExp("^" + util.escapeRegExp(path.replace(/^~/, c9.home) + "/"), "gm"); - find.getFileList({ - path: path, - nocache: true, - buffer: true - }, function(err, data) { - if (err) - return next(err); - - // Remove the base path - data = data.replace(rePath, ""); - - if (data.match(/^__installed__.js/)) - return next("installed"); - - // Process all the submodules - var parallel = processModules(path, data, resourceHolder); - async.parallel(parallel, function(err, data) { - if (err) - return next(err); - - if (!inited) - resourceHolder.load(name + ".bundle"); - - // Done - next(); - }); - }); - } - ], function(err, results) { - if (err) console.error(err); - - if (!inited) { - next(); - inited = true; - } - }); - } - - load(); - }, function() { - emit.sticky("ready"); - - if (!config.length) return; - - // Load config - if (installer.sessions.length) { - installer.on("stop", function listen(err) { - if (err) - return console.error(err); - - if (!installer.sessions.length) { - loadConfig(); - installer.off("stop", listen); - } - }); - return; - } - - loadConfig(); - }); - } - - function processModules(path, data, plugin) { - var parallel = []; - - data.split("\n").forEach(function(line) { - if (!line.match(reParts)) return; - - var type = RegExp.$1; - var filename = RegExp.$2; - if (filename.indexOf("/") > -1) return; - - if (type == "modes" && (reModule.test(filename) || !jsExtRe.test(filename))) - return; - - if (type == "snippets") { - if (jsExtRe.test(filename)) { - var snippetPath = join("plugins", basename(path), type, filename).replace(jsExtRe, ""); - require([snippetPath], function(m) { - architect.services["language.complete"].addSnippet(m, plugin); - }); - } - if (!/\.snippets$/.test(filename)) - return; - } - - parallel.push(function(next) { - fs.readFile(join(path, type, filename), function(err, data) { - if (err) { - console.error(err); - return next(err); - } - - addStaticPlugin(type, basename(path), filename, data, plugin); - - next(); - }); - }); - }); - - return parallel; - } - - function addStaticPlugin(type, pluginName, filename, data, plugin) { - var services = architect.services; - var path = "plugins/" + pluginName + "/" - + (type == "installer" ? "" : type + "/") - + filename.replace(/\.js$/, ""); - - var bundleName = pluginName + ".bundle"; - if (!services[bundleName] && type !== "installer") { - services[bundleName] = plugin; - architect.lut["~/.c9/plugins/" + pluginName] = { - provides: [] - }; - architect.pluginToPackage[bundleName] = { - path: "~/.c9/plugins/" + pluginName, - package: pluginName, - version: plugin.version, - isAdditionalMode: true - }; - if (!architect.packages[pluginName]) - architect.packages[pluginName] = []; - architect.packages[pluginName].push(name); - } - - switch (type) { - case "builders": - data = util.safeParseJson(data, function() {}); - if (!data) return; - - services.build.addBuilder(filename, data, plugin); - break; - case "keymaps": - data = util.safeParseJson(data, function() {}); - if (!data) return; - - services["preferences.keybindings"].addCustomKeymap(filename, data, plugin); - break; - case "modes": - var mode = {}; - var firstLine = data.split("\n", 1)[0].replace(/\/\*|\*\//g, "").trim(); - firstLine.split(";").forEach(function(n) { - if (!n) return; - var info = n.split(":"); - mode[info[0].trim()] = info[1].trim(); - }); - - services.ace.defineSyntax({ - name: path, - caption: mode.caption, - extensions: (mode.extensions || "").trim() - .replace(/\s*,\s*/g, "|").replace(/(^|\|)\./g, "$1") - }); - break; - case "outline": - if (!data) return; - - services.outline.addOutlinePlugin(path, data, plugin); - break; - case "runners": - data = util.safeParseJson(data, function() {}); - if (!data) return; - - services.run.addRunner(data.caption || filename, data, plugin); - break; - case "snippets": - services["language.complete"].addSnippet(data, plugin); - break; - case "themes": - services.ace.addTheme(data, plugin); - break; - case "templates": - services.newresource.addFileTemplate(data, plugin); - break; - case "installer": - if (data) { - installer.createSession(pluginName, data, function(v, o) { - require([path], function(fn) { - fn(v, o); - }); - }); - } - else { - require([path], function(fn) { - installer.createSession(pluginName, fn.version, function(v, o) { - fn(v, o); - }); - }); - } - } - } - - // Check if require.s.contexts._ can help watching all dependencies - function watch(path) { - watcher.watch(path); - - watcher.on("change", function(e) { - if (e.path == path) - reloadPackage(path.replace(/^~\/\.c9\//, "")); - }); - watcher.on("delete", function(e) { - if (e.path == path) - reloadPackage(path.replace(/^~\/\.c9\//, "")); - }); - watcher.on("failed", function(e) { - if (e.path == path) { - setTimeout(function() { - watcher.watch(path); // Retries once after 1s - }); - } - }); - } - - function reloadPackage(path) { - var unloaded = []; - - function recurUnload(name) { - var plugin = architect.services[name]; - unloaded.push(name); - - // Find all the dependencies - var deps = ext.getDependencies(plugin.name); - - // Unload all the dependencies (and their deps) - deps.forEach(function(name) { - recurUnload(name); - }); - - // Unload plugin - plugin.unload(); - } - - // Recursively unload plugin - var p = architect.lut[path]; - if (p.provides) { // Plugin might not been initialized all the way - p.provides.forEach(function(name) { - recurUnload(name); - }); - } - - // create reverse lookup table - var rlut = {}; - for (var packagePath in architect.lut) { - var provides = architect.lut[packagePath].provides; - if (provides) { // Plugin might not been initialized all the way - provides.forEach(function(name) { - rlut[name] = packagePath; - }); - } - } - - // Build config of unloaded plugins - var config = [], done = {}; - unloaded.forEach(function(name) { - var packagePath = rlut[name]; - - // Make sure we include each plugin only once - if (done[packagePath]) return; - done[packagePath] = true; - - var options = architect.lut[packagePath]; - delete options.provides; - delete options.consumes; - delete options.setup; - - config.push(options); - - // Clear require cache - requirejs.undef(options.packagePath); // global - }); - - // Load all plugins again - architect.loadAdditionalPlugins(config, function(err) { - if (err) console.error(err); - }); - } - - function reloadPluginUI() { - var list = []; - Object.keys(architect.pluginToPackage).forEach(function(name) { - if (architect.pluginToPackage[name].isAdditionalMode) - list.push(architect.pluginToPackage[name].path); - }); - - var path = list[list.length - 1]; - reloadPackage(path.replace(/^~\/\.c9\//, "")); - - // Avoid confusion with "Reload Last Plugin" - var href = document.location.href.replace(/[?&]reload=[^&]+/, ""); - window.history.replaceState(window.history.state, null, href); - showInfo("Reloaded " + path + ".", 1000); - } - - /***** Lifecycle *****/ - - plugin.on("load", function() { - load(); - }); - plugin.on("unload", function() { - loaded = false; - }); - - /***** Register and define API *****/ - - /** - * - **/ - plugin.freezePublicAPI({ - /** - * - */ - get architect() { throw new Error(); }, - set architect(v) { architect = v; }, - /** - * - */ - get plugins() { return plugins; }, - - _events: [ - /** - * @event ready - */ - "ready" - ], - - /** - * - */ - addStaticPlugin: addStaticPlugin, - - /** - * - */ - reloadPackage: reloadPackage, - /** - * - */ - loadPackage: loadPlugins - }); - - register(null, { - "plugin.debug": plugin - }); - } -}); diff --git a/plugins/c9.ide.plugins/gui.js b/plugins/c9.ide.plugins/gui.js new file mode 100644 index 00000000..cc0bf006 --- /dev/null +++ b/plugins/c9.ide.plugins/gui.js @@ -0,0 +1,959 @@ +define(function(require, exports, module) { + main.consumes = [ + "app", "ext", "c9", "PreferencePanel", "settings", "ui", "util", + "layout", "menus", "commands", "pluginManager", + "dialog.error", "dialog.info", "tree.favorites", "fs", "tree", "vfs", + "preferences.experimental", "apf","dialog.notification" + ]; + main.provides = ["pluginManagerUi"]; + return main; + + + function main(options, imports, register) { + var PreferencePanel = imports.PreferencePanel; + var settings = imports.settings; + var layout = imports.layout; + var commands = imports.commands; + var menus = imports.menus; + var ui = imports.ui; + var c9 = imports.c9; + var fs = imports.fs; + var ext = imports.ext; + var util = imports.util; + var apf = imports.apf; + var showError = imports["dialog.error"].show; + var showInfo = imports["dialog.info"].show; + var notify = imports["dialog.notification"].show; + var experimental = imports["preferences.experimental"]; + var pluginManager = imports.pluginManager; + var architectApp = imports.app; + + var search = require("../c9.ide.navigate/search"); + var Tree = require("ace_tree/tree"); + var TreeData = require("./managerdp"); + var qs = require("querystring"); + + var escapeHTML = require("ace/lib/lang").escapeHTML; + + var CORE = {}; + var TEMPLATES = { + "plugin.simple": "Empty Plugin", + "plugin.default": "Full Plugin", + "plugin.installer": "Installer Plugin", + "plugin.bundle": "Cloud9 Bundle" + }; + + /***** Initialization *****/ + + var DEBUG = c9.location.indexOf("debug=2") > -1; + var ENABLED = DEBUG || experimental.addExperiment( + "plugin-manager", + options.devel, + "SDK/Plugin Manager" + ); + + var plugin = new PreferencePanel("Ajax.org", main.consumes, { + caption: "Plugin Explorer", + className: "plugins", + form: false, + noscroll: true, + index: 200, + visible: ENABLED, + }); + var emit = plugin.getEmitter(); + + var model; + var datagrid; + var filterbox; + var btnInstall; + var btnUninstall; + var btnServices; + var btnReadme; + var btnReloadLast; + var localPlugins; + var btnSettings; + + var loaded = false; + function load() { + if (loaded) return false; + loaded = true; + + menus.addItemByPath("Tools/~", new ui.divider(), 100000, plugin); + menus.addItemByPath("Tools/Developer", null, 100100, plugin); + + if (!DEBUG) { + menus.addItemByPath("Tools/Developer/Start in Debug Mode", new ui.item({ + onclick: function() { + var url = location.href + (location.href.indexOf("?") > -1 + ? "&debug=2" + : "?debug=2"); + util.openNewWindow(url); + } + }), 900, plugin); + } + if (!ENABLED) { + return; + } + + commands.addCommand({ + name: "openPluginManager", + group: "Plugins", + exec: function() { + commands.exec("openpreferences", null, { panel: plugin }); + } + }, plugin); + + menus.addItemByPath("Tools/Developer/Open Plugin Explorer", new ui.item({ + command: "openPluginManager" + }), 1100, plugin); + + if (DEBUG) { + notify("
You are in Debug Mode. " + + "" + + "", + false); + + document.getElementById(".pm_btn1").onclick = function() { commands.exec("openPluginManager") }; + btnReloadLast = document.getElementById(".pm_btn2"); + btnReloadLast.onclick = function() { commands.exec("reloadLastPlugin") }; + updateReloadLastButton(); + + pluginManager.readAvailablePlugins(function(err, available) { + if (err) return console.error(err); + localPlugins = available; + reloadModel(); + if (sessionStorage.localPackages) { + pluginManager.loadPackage( + util.safeParseJson(sessionStorage.localPackages) || [] + ); + } + var updateSessionStorage = function() { + var packages = pluginManager.packages; + sessionStorage.localPackages = JSON.stringify(Object.keys(packages).map(function(x) { + if (packages[x] && packages[x].fromVfs && packages[x].enabled) + return packages[x].filePath; + }).filter(Boolean)); + }; + pluginManager.on("enablePackage", updateSessionStorage); + pluginManager.on("disablePackage", updateSessionStorage); + }); + + commands.addCommand({ + name: "reloadLastPlugin", + bindKey: { mac: "F4", win: "F4" }, + hint: "reload plugin last reloaded in plugin manager", + exec: function() { + var names = getLastReloaded(); + if (!names) + return commands.exec("openPluginManager", null, { panel: plugin }); + reload(names); + } + }, plugin); + + menus.addItemByPath("Tools/Developer/Reload Last Plugin", new ui.item({ + command: "reloadLastPlugin", + isAvailable: getLastReloaded + }), 1300, plugin); + } + + menus.addItemByPath("File/New Plugin", null, 210, plugin); + Object.keys(TEMPLATES).forEach(function(name) { + menus.addItemByPath("File/New Plugin/" + TEMPLATES[name], new ui.item({ + onclick: function() { + pluginManager.createNewPlugin(name); + } + }), 210, plugin); + }); + } + + var drawn; + function draw(e) { + if (drawn) return; + drawn = true; + + function scheduleReloadModel() { + if (scheduleReloadModel.timer) return; + scheduleReloadModel.timer = setTimeout(function() { + scheduleReloadModel.timer = null; + reloadModel(); + }); + } + ext.on("register", scheduleReloadModel); + pluginManager.on("change", scheduleReloadModel); + pluginManager.on("enablePackage", scheduleReloadModel); + pluginManager.on("disablePackage", scheduleReloadModel); + + ui.insertCss(require("text!./style.css"), plugin); + + model = new TreeData(); + model.emptyMessage = "No plugins found"; + + model.columns = [{ + caption: "Name", + value: "name", + width: "250", + type: "tree" + }, { + caption: "Startup Time", + // value: "time", + width: "100", + getText: function(p) { + if (p.time !== undefined) + return (p.time || 0) + "ms"; + + var total = 0; + function recur(p) { + if (p.time != undefined) + return total += p.time; + if (p.items) + p.items.forEach(recur); + } + recur(p); + return (p.time = total) + "ms"; + } + }, { + caption: "Enabled", + value: "enabled", + width: "100" + }]; + model.columns = null; + + layout.on("eachTheme", function(e) { + var height = parseInt(ui.getStyleRule(".bar-preferences .blackdg .tree-row", "height"), 10) || 24; + model.rowHeightInner = height; + model.rowHeight = height; + + if (e.changed) datagrid.resize(true); + }); + + architectApp.on("ready-additional", function() { + reloadModel(); + }); + reloadModel(); + + var hbox = new ui.hbox({ + htmlNode: e.html, + padding: 5, + edge: "10 10 0 10", + childNodes: [ + filterbox = new apf.codebox({ + realtime: true, + skin: "codebox", + "class": "dark", + clearbutton: true, + focusselect: true, + height: 27, + width: 250, + singleline: true, + "initial-message": "Search installed plugins" + }), + btnReadme = new ui.button({ + skin: "c9-toolbarbutton-glossy", + caption: "Readme", + class: "serviceButton", + onclick: function() { + mode = "readme"; + scheduleRedraw(); + } + }), + btnServices = new ui.button({ + skin: "c9-toolbarbutton-glossy", + caption: "services", + class: "serviceButton", + onclick: function() { + mode = "services"; + scheduleRedraw(); + } + }), + new ui.filler({}), + btnInstall = new ui.button({ + skin: "c9-toolbarbutton-glossy", + caption: "Enable", + class: "btn-red", + onclick: function() { + reloadGridSelection(true); + } + }), + btnUninstall = new ui.button({ + skin: "c9-toolbarbutton-glossy", + caption: "Disable", + class: "btn-red", + onclick: function() { + reloadGridSelection(false); + } + }), + new ui.button({ + skin: "c9-toolbarbutton-glossy", + caption: "Reload", + onclick: function() { + reloadGridSelection(); + } + }) + ] + }); + var treeBar; + var descriptionBar; + var hboxInner = new ui.hsplitbox({ + anchor: "0 0 0 0", + htmlNode: e.html, + class: "bar-preferences", + splitter: true, + childNodes: [ + treeBar = new ui.bar({ width: 250 }), + descriptionBar = new ui.bar({ textselect: true }), + ] + }); + + var div = treeBar.$ext.appendChild(document.createElement("div")); + div.style.position = "absolute"; + div.style.left = "0px"; + div.style.right = "0px"; + div.style.bottom = "0px"; + div.style.top = "0px"; + hboxInner.$ext.style.position = "absolute"; + hboxInner.$ext.style.left = "10px"; + hboxInner.$ext.style.right = "10px"; + hboxInner.$ext.style.bottom = "10px"; + hboxInner.$ext.style.top = "50px"; + + datagrid = new Tree(div); + datagrid.setTheme({ cssClass: "blackdg" }); + datagrid.setDataProvider(model); + + layout.on("resize", function() { datagrid.resize() }, plugin); + + function setTheme(e) { + filterbox.setAttribute("class", + e.theme.indexOf("dark") > -1 ? "dark" : ""); + } + layout.on("themeChange", setTheme); + setTheme({ theme: settings.get("user/general/@skin") }); + + filterbox.ace.commands.addCommands([ + { + bindKey: "Enter", + exec: function() { } + }, { + bindKey: "Esc", + exec: function(ace) { ace.setValue(""); } + } + ]); + + filterbox.ace.on("input", function(e) { + applyFilter(); + }); + + // when tab is restored datagrids size might be wrong + // todo: remove this when apf bug is fixed + datagrid.once("mousemove", function() { + datagrid.resize(true); + }); + + datagrid.on("changeSelection", scheduleRedraw); + model.on("change", scheduleRedraw); + + plugin.on("reloadModel", scheduleRedraw); + + model.getCheckboxHTML = function(node) { + var enabled = node.enabled; + if (enabled == null || node.isGroup) return ""; + return ""; + }; + + var mode = "services"; + var readmeCache = {}; + function renderDetails() { + var items = datagrid.selection.getSelectedNodes(); + var hasEnabled = 0; + var hasDisabled = 0; + items.forEach(function(x) { + if (x.enabled != 0) + hasEnabled = true; + if (x.enabled != 1) + hasDisabled = true; + }); + btnUninstall.setProperty("visible", hasEnabled); + btnInstall.setProperty("visible", hasDisabled); + + if (mode == "services") { + renderServiceDetails(items); + btnServices.$ext.classList.add("serviceButtonActive"); + btnReadme.$ext.classList.remove("serviceButtonActive"); + } + else if (mode == "readme") { + renderReadmeDetails(items); + btnReadme.$ext.classList.add("serviceButtonActive"); + btnServices.$ext.classList.remove("serviceButtonActive"); + } + else { + descriptionBar.$ext.innerHTML = ""; + btnReadme.$ext.classList.remove("serviceButtonActive"); + btnServices.$ext.classList.remove("serviceButtonActive"); + } + + if (items.length == 1 && items[0].__error) { + var errorContainer = document.createElement("div"); + errorContainer.style.cssText = "padding: 5px; white-space: pre-wrap"; + errorContainer.textContent = items[0].__error.message; + descriptionBar.$ext.insertBefore(errorContainer, descriptionBar.$ext.firstChild); + } + } + + function renderReadmeDetails(items) { + if (items.length > 1) { + descriptionBar.$ext.textContent = "Multipe items selected."; + return; + } + var item = items[0]; + while (item && !item.packageConfig) { + item = item.parent; + } + if (!item) { + descriptionBar.$ext.textContent = "Readme is not available."; + return; + } + + if (readmeCache[item.name]) { + var div = document.createElement("div"); + div.textContent = readmeCache[item.name]; + div.style.cssText = "padding: 5px; white-space: pre-wrap"; + descriptionBar.$ext.textContent = ""; + descriptionBar.$ext.appendChild(div); + return; + } + descriptionBar.$ext.textContent = "Loading..."; + if (item.packageConfig.filePath) { + var parts = item.packageConfig.filePath.split("/"); + parts.pop(); + parts.push("README.md"); + var path = parts.join("/"); + fs.readFile(path, function(e, v) { + if (e) + return readmeCache[item.name] = "Readme is not available."; + readmeCache[item.name] = v; + renderDetails(); + }); + } + } + + function renderServiceDetails(items) { + function addUnique(item, array) { + if (array.indexOf(item) == -1) array.push(item); + } + + var provided = []; + + items.forEach(function addProvides(x) { + if (x.map) { + Object.keys(x.map).forEach(function(n) { + addProvides(x.map[n]); + }); + } + if (x.provides) { + x.provides.forEach(function(p) { + addUnique(p, provided); + }); + } + }); + + var consumedMap = Object.create(null); + provided.forEach(function(service) { + consumedMap[service] = 0; + }); + pluginManager.addAllProviders(consumedMap); + var consumedList = Object.keys(consumedMap); + var consumedGroups = splitToGroups(consumedList, consumedMap); + + var dependents = Object.create(null); + provided.forEach(function(service) { + dependents[service] = 0; + }); + pluginManager.addAllDependents(dependents); + var depList = Object.keys(dependents); + var depGroups = splitToGroups(depList, dependents); + + function splitToGroups(list, map) { + var groups = []; + list.forEach(function(n) { + var level = Math.abs(map[n]); + if (!groups[level]) + groups[level] = []; + groups[level].push(n); + }); + groups = groups.filter(Boolean).slice(1); + return groups; + } + + function formatServices(list, style) { + return "[" + list.map(function(x) { + return '' + escapeHTML(x) + ''; + }).join(", ") + "]"; + } + + descriptionBar.$ext.style.overflow = "auto"; + var serviceDesc = '

Provided services [' + provided.length + ']

\ +

' + (provided.length ? formatServices(provided) : "") + '

\ +

Consumed services [' + (consumedList.length - provided.length) + ']

\ +

' + consumedGroups.map(formatServices).join("
") + '

\ +

Dependent services [' + (depList.length - provided.length) + ']

\ +

' + depGroups.map(formatServices).join("
") + '

\ +
'; + descriptionBar.$ext.innerHTML = '
\ +

' + (items.length == 1 + ? '' + escapeHTML(items[0].path) + '' + : (items.length || "no") + " plugins selected") + '

\ +
' + + (items.length ? serviceDesc : "") + + '
'; + + } + descriptionBar.$ext.addEventListener("click", function(e) { + if (e.button) return; + var el = e.target; + var isButton = el.classList.contains("serviceButton"); + var text = el.textContent; + var serviceToPlugin = architectApp.serviceToPlugin; + if (e.detail == 1 && isButton && text) { + var path = serviceToPlugin[text] ? serviceToPlugin[text].packagePath : text; + var node = function search(node) { + if (node.path == path) + return node; + if (node.items) { + for (var i = 0; i < node.items.length; i++) { + var result = search(node.items[i]); + if (result) + return result; + } + } + }(model.cachedRoot); + + if (node) + datagrid.reveal(node); + } + }); + var hoverDetails = { hovered: "", highlighted: "" }; + descriptionBar.$ext.addEventListener("mousemove", function(e) { + if (e.button) return; + var el = e.target; + var isButton = el.classList.contains("serviceButton"); + var text = el.textContent; + hoverDetails.parent = el.parentNode; + hoverDetails.hovered = isButton ? text : ""; + hoverDetails.type = isButton ? el.parentNode.getAttribute("type") : ""; + if (hoverDetails.hovered != hoverDetails.highlighted) + requestAnimationFrame(updateHighlights); + }); + function updateHighlights() { + if (hoverDetails.hovered == hoverDetails.highlighted) return; + var serviceToPlugin = architectApp.serviceToPlugin; + if (!serviceToPlugin) return; + + var nodes = descriptionBar.$ext.querySelectorAll(".serviceButton"); + + var deps = Object.create(null); + deps[hoverDetails.hovered] = 0; + pluginManager.addAllDependents(deps); + pluginManager.addAllProviders(deps); + + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + var val = node.textContent; + var highlight1 = false; + var highlight2 = false; + if (hoverDetails.type == "dependent") { + if (hoverDetails.parent == node.parentNode || node.parentNode.getAttribute("type") == "provided") { + highlight1 = deps[val] < 0; + highlight2 = deps[val] > 0; + } + } else if (hoverDetails.type == "consumed") { + if (hoverDetails.parent == node.parentNode || node.parentNode.getAttribute("type") == "provided") { + highlight1 = deps[val] > 0; + highlight2 = deps[val] < 0; + } + } + + nodes[i].style.borderBottom = highlight1 ? "1px solid" : ""; + if (!highlight1) { + nodes[i].style.borderBottom = highlight2 ? "1px dashed rgba(125, 125, 125, 0.9)" : ""; + } + } + hoverDetails.highlighted = hoverDetails.hovered; + } + + function scheduleRedraw() { + window.requestAnimationFrame(renderDetails); + } + + var mnuCtxTree = new ui.menu({ + id: "mnuChat", + }, plugin); + menus.decorate(mnuCtxTree); + plugin.addElement(mnuCtxTree); + + + btnSettings = new ui.button({ + skin: "header-btn", + class: "panel-settings", + submenu: mnuCtxTree + }); + treeBar.appendChild(btnSettings); + datagrid.renderer.on("scrollbarVisibilityChanged", updateScrollBarSize); + function updateScrollBarSize() { + var scrollBarV = datagrid.renderer.scrollBarV; + var w = scrollBarV.isVisible ? scrollBarV.getWidth() : 0; + btnSettings.$ext.style.marginRight = Math.max(w - 2, 0) + "px"; + } + + menus.addItemByPath("context/pluginManager/", mnuCtxTree, 0, plugin); + menus.addItemByPath("context/pluginManager/Reveal in File Tree", new ui.item({ + isAvailable: function() { + var selected = datagrid.selection.getCursor(); + return selected && selected.packageConfig && selected.packageConfig.filePath + || c9.sourceDir && selected && selected.path; + }, + onclick: function() { + var selected = datagrid.selection.getCursor(); + var tabbehavior = architectApp.services.tabbehavior; + var filePath = selected.packageConfig && selected.packageConfig.filePath; + if (!filePath && c9.sourceDir) + filePath = c9.sourceDir + "/" + selected.path + (selected.items ? "" : ".js"); + if (filePath) + tabbehavior.revealtab({ path: util.normalizePath(filePath) }); + else + showInfo("Path is not available."); + }, + }), plugin); + menus.addItemByPath("context/pluginManager/Refresh List", new ui.item({ + onclick: function() { + pluginManager.readAvailablePlugins(function(err, available) { + if (err) return console.error(err); + localPlugins = available; + reloadModel(); + }); + }, + }), plugin); + menus.addItemByPath("context/pluginManager/~", new ui.divider({}), plugin); + menus.addItemByPath("context/pluginManager/Disable", new ui.item({ + isAvailable: function() { + var selected = datagrid.selection.getCursor(); + return selected && selected.enabled != 0; + }, + onclick: function() { reloadGridSelection(false); }, + }), plugin); + menus.addItemByPath("context/pluginManager/Enable", new ui.item({ + isAvailable: function() { + var selected = datagrid.selection.getCursor(); + return selected && selected.enabled != 1; + }, + onclick: function() { reloadGridSelection(true); }, + }), plugin); + menus.addItemByPath("context/pluginManager/Reload", new ui.item({ + isAvailable: function() { + var selected = datagrid.selection.getCursor(); + return selected; + }, + onclick: function() { reloadGridSelection(); }, + }), plugin); + treeBar.setAttribute("contextmenu", mnuCtxTree); + + model.on("toggleCheckbox", function(e) { + reloadGridSelection(!e.target.enabled, e.selectedNodes || [e.target]); + }); + } + + + /***** Methods *****/ + + function reloadModel() { + if (!model) return; + + var packages = pluginManager.packages; + + if (!CORE.pluginManagerUi) { + CORE.pluginManagerUi = 1; + pluginManager.addAllProviders(CORE); + Object.keys(CORE).forEach(function(n) { + if (architectApp.serviceToPlugin[n]) + CORE[architectApp.serviceToPlugin[n].packagePath] = 1; + }); + } + + var GROUPS = { + // "changed": "Recently Changed", + "remote": "Remote Plugins", + "vfs": "Locally Installed Plugins", + "pre": "Pre-installed Plugins", + "core": "Core Plugins", + }; + + var groups = model.groups || Object.create(null); + if (!model.groups) { + var root = []; + model.cachedRoot = { items: root }; + model.groups = groups; + Object.keys(GROUPS).forEach(function(name) { + root.push(groups[name] = { + map: Object.create(null), + isOpen: name != "runtime", + className: "group", + isGroup: true, + isType: name, + noSelect: true, + name: GROUPS[name] + }); + }); + } + + function addPlugin(plugin, node) { + if (!plugin.provides || !plugin.consumes) + return; + if (plugin.packagePath) { + var parts = plugin.packagePath.split("/"); + + var path = parts.shift(); + parts.forEach(function(p, i) { + path = path + "/" + p; + if (!node.map) + node.map = Object.create(null); + if (!node.map[p]) { + node.map[p] = { + path: path, + parent: node, + }; + } + node.map[p].name = p; + if (i == parts.length - 1) { + var enabled = 1; + node.map[p].provides = plugin.provides; + plugin.provides.forEach(function(x) { + var service = architectApp.services[x]; + if (!service || (!service.loaded && service.unload)) { + enabled = 0; + } + }); + node.map[p].enabled = enabled; + } + node = node.map[p]; + node.className = plugin.__error ? "load-error" : ""; + node.__error = plugin.__error; + }); + } + } + + function addPackage(name) { + var pkg = packages[name]; + var parent = pkg.filePath ? groups.vfs : groups.remote; + + var node = parent.map[name] = parent.map[name] || { + path: "plugins/" + name, + name: name, + enabled: 0, + parent: parent, + }; + + node.packageConfig = pkg; + node.className = pkg.__error ? "load-error" : ""; + node.__error = pkg.__error; + node.loading = pkg.loading; + } + + architectApp.config.forEach(function(plugin) { + var node = CORE[plugin.packagePath] ? groups.core : groups.pre; + addPlugin(plugin, node); + }); + + if (localPlugins) { + localPlugins.forEach(function(name) { + if (!packages[name]) { + packages[name] = { + name: name, + filePath: "~/.c9/plugins/" + name + "/package.json", + }; + } + + }); + } + + Object.keys(packages).forEach(function(n) { + var pkg = packages[n]; + var node = pkg.filePath ? groups.vfs : groups.remote; + + addPackage(n); + + if (pkg.c9 && pkg.c9.plugins) { + pkg.c9.plugins.forEach(function(plugin) { + addPlugin(plugin, node); + }); + } + }); + + function flatten(node, index) { + if (node.map) { + node.items = node.children = Object.keys(node.map).map(function(x) { + return node.map[x]; + }); + } + if (node.items) { + node.items.forEach(flatten); + if (node.items.length == 1 && !node.isGroup) { + var other = node.items[0]; + if (node.parent && node.parent.items[index] == node) { + node.parent.items[index] = other; + other.name = node.name + "/" + other.name; + other.packageConfig = node.packageConfig; + other.filePath = node.filePath; + other.url = node.url; + other.loading = node.loading; + } + } + if (!node.isGroup) { + node.enabled = null; + node.items.some(function(i) { + if (node.enabled == null) { + node.enabled = i.enabled; + } + if (i.enabled != node.enabled) { + node.enabled = -1; + return true; + } + }); + } + } + } + + flatten(model.cachedRoot); + + applyFilter(); + + emit("reloadModel"); + } + + function applyFilter() { + model.keyword = filterbox && filterbox.getValue(); + + if (!model.keyword) { + model.reKeyword = null; + model.setRoot(model.cachedRoot); + + // model.isOpen = function(node) { return node.isOpen; } + } + else { + model.reKeyword = new RegExp("(" + + util.escapeRegExp(model.keyword) + ")", 'i'); + var root = search.treeSearch(model.cachedRoot.items, model.keyword, true); + model.setRoot(root); + + // model.isOpen = function(node) { return true; }; + } + } + + function reload(names) { + var nodes = names.split(/\s*,\s*/).map(function(name) { + if (pluginManager.packages[name]) + return { packageConfig: pluginManager.packages[name] }; + return { path: name }; + }); + + reloadGridSelection(null, nodes); + } + + function reloadGridSelection(mode, nodes) { + if (!nodes) + nodes = datagrid.selection.getSelectedNodes(); + + var reloadLast = pluginManager.reload(nodes, mode); + if (reloadLast.length) { + var href = document.location.href.replace(/[?&]reload=[^&]+/, ""); + href += (href.match(/\?/) ? "&" : "?") + "reload=" + reloadLast.join(","); + window.history.replaceState(window.history.state, null, href); + if (mode !== false) { + showReloadTip(reloadLast.join(","), mode); + updateReloadLastButton(); + } + } + } + + function updateReloadLastButton() { + var last = getLastReloaded(); + if (last) { + btnReloadLast.visible = true; + btnReloadLast.textContent = "Reload " + last; + } else { + btnReloadLast.visible = false; + } + } + + function showReloadTip(name, mode) { + if (options.devel) { + var key = commands.getPrettyHotkey("reloadLastPlugin"); + if (!getLastReloaded()) { + showInfo("Loaded " + name + ". Press " + key + " to reload again.", 3000); + return; + } + } + showInfo("Loaded " + name + " for the duration of current browser session.", 3000); + } + + function getLastReloaded() { + return qs.parse(document.location.search.substr(1)).reload; + } + + + /***** Lifecycle *****/ + + plugin.on("load", function() { + load(); + }); + plugin.on("draw", function(e) { + draw(e); + }); + plugin.on("activate", function(e) { + datagrid && datagrid.resize(); + }); + plugin.on("resize", function(e) { + datagrid && datagrid.resize(); + }); + plugin.on("enable", function() { + + }); + plugin.on("disable", function() { + + }); + plugin.on("unload", function() { + loaded = false; + drawn = false; + btnServices = null; + btnReadme = null; + btnReloadLast = null; + model = null; + datagrid = null; + filterbox = null; + btnInstall = null; + btnUninstall = null; + localPlugins = null; + }); + + /***** Register and define API *****/ + + /** + * + **/ + plugin.freezePublicAPI({ + + /* + * @ignore + */ + get datagrid() { return datagrid; }, + }); + + register(null, { + "pluginManagerUi": plugin, + }); + } +}); diff --git a/plugins/c9.ide.plugins/installer.js b/plugins/c9.ide.plugins/installer.js deleted file mode 100644 index 6a6abff8..00000000 --- a/plugins/c9.ide.plugins/installer.js +++ /dev/null @@ -1,196 +0,0 @@ -define(function(require, exports, module) { - main.consumes = [ - "Plugin", "proc", "c9", "pubsub", "auth", "util", "installer", - "preferences.experimental" - ]; - main.provides = ["plugin.installer"]; - return main; - - function main(options, imports, register) { - var Plugin = imports.Plugin; - var c9 = imports.c9; - var util = imports.util; - var proc = imports.proc; - var auth = imports.auth; - var pubsub = imports.pubsub; - var installer = imports.installer; - var experimental = imports["preferences.experimental"]; - - var async = require("async"); - - var escapeShell = util.escapeShell; - var updates = options.updates; - var architect; - - /***** Initialization *****/ - - var plugin = new Plugin("Ajax.org", main.consumes); - var emit = plugin.getEmitter(); - - var DEBUG_MODE = c9.location.indexOf("debug=2") > -1; - var HASSDK = DEBUG_MODE || experimental.addExperiment("sdk", false, "SDK/Load Custom Plugins"); - - var installing; - - var loaded = false; - function load() { - if (loaded) return false; - loaded = true; - - if (!HASSDK) return; - - pubsub.on("message", function(message) { - if (message.type != "package") - return; - - console.log("PubSub package API message", message); - var action = message.action; - var body = message.body; - - // Only accept packages that are installed for this project - if (body.pid && body.pid != c9.projectId) - return; - - // Only accept packages that are installed for this user - if (body.uid && body.uid != c9.uid) - return; - - if (action == "install") { - installPlugins([body.config], function() {}); - } - else if (action == "uninstall") { - uninstallPlugin(body.name, function() {}); - } - }); - - installPlugins(updates); - } - - /***** Methods *****/ - - function installPlugins(config, callback) { - // if (!vfs.connected) { - // vfs.once("connect", loadPackages.bind(this, config)); - // return; - // } - - if (!config.length) - return callback && callback(); - - // Only run one installer at a time - if (installing) { - return plugin.once("finished", function() { - installPlugins(config, callback); - }); - } - - installing = true; - - var found = {}, packages = []; - config.forEach(function(item) { - if (typeof item === "string") { - item = { name: item, version: null }; - } - - if (found[item.name]) return; - found[item.name] = true; - - packages.push({ name: item.name, version: item.version }); - }); - - async.eachSeries(packages, function(pkg, next) { - installPlugin(pkg.name, pkg.version, next); - }, function(err) { - installing = false; - emit("finished"); - - if (err) { - console.error(err.message); - return callback && callback(err); - } - - architect.loadAdditionalPlugins(config, callback); - }); - } - - function installPlugin(name, version, callback) { - // Headless installation of the plugin - installer.createSession(name, version, function(session, options) { - var cmd = [ - "c9", - "install", - "--local", - "--force", - "--accessToken=" + auth.accessToken, - ]; - - if (version == null) - cmd.push(escapeShell(name)); - else - cmd.push(escapeShell(name + "@" + version)); - - session.install({ - "bash": cmd.join(" ") - }); - - // Force to start immediately - session.start(callback, true); - }, function() {}, 2); // Force to not be administered - } - - function uninstallPlugin(name, callback) { - // Headless uninstallation of the plugin - installer.createSession(name, -1, function(session, options) { - session.install({ - "bash": "c9 remove --local --force --accessToken=" + auth.accessToken - + " " + escapeShell(name) - }); - - // Force to start immediately - session.start(callback, true); - }, function() {}, 2); // Force to not be administered - } - - /***** Lifecycle *****/ - - plugin.on("load", function() { - load(); - }); - plugin.on("unload", function() { - loaded = false; - installing = false; - }); - - /***** Register and define API *****/ - - /** - * - **/ - plugin.freezePublicAPI({ - /** - * - */ - get architect() { throw new Error(); }, - set architect(v) { architect = v; }, - - /** - * - */ - installPlugins: installPlugins, - - /** - * - */ - installPlugin: installPlugin, - - /** - * - */ - uninstallPlugin: uninstallPlugin - }); - - register(null, { - "plugin.installer": plugin - }); - } -}); \ No newline at end of file diff --git a/plugins/c9.ide.plugins/loader.js b/plugins/c9.ide.plugins/loader.js deleted file mode 100644 index 08411773..00000000 --- a/plugins/c9.ide.plugins/loader.js +++ /dev/null @@ -1,340 +0,0 @@ -/*global requirejs*/ -define(function(require, exports, module) { - main.consumes = [ - "Plugin", "vfs", "c9", "plugin.installer", "fs", "auth", - "preferences.experimental" - ]; - main.provides = ["plugin.loader"]; - return main; - - function main(options, imports, register) { - var Plugin = imports.Plugin; - var vfs = imports.vfs; - var c9 = imports.c9; - var fs = imports.fs; - var auth = imports.auth; - var installer = imports["plugin.installer"]; - var experimental = imports["preferences.experimental"]; - - var dirname = require("path").dirname; - var join = require("path").join; - var async = require("async"); - var _ = require("lodash"); - - var architect; - - /***** Initialization *****/ - - var plugin = new Plugin("Ajax.org", main.consumes); - // var emit = plugin.getEmitter(); - - var DEBUG_MODE = c9.location.indexOf("debug=2") > -1; - var ENABLED = DEBUG_MODE || experimental.addExperiment("plugins", false, "SDK/Load Plugins From Workspace"); - var HASSDK = DEBUG_MODE || experimental.addExperiment("sdk", false, "SDK/Load Custom Plugins"); - - var plugins = options.plugins; - var loadFromDisk = options.loadFromDisk; - - var names = []; - - function load() { - if (!HASSDK) return; - if (!ENABLED) return; - - loadPlugins(plugins); - } - - /***** Methods *****/ - - // TODO the resolution alghoritm used here is very inefficient - // ideally we will use a simpler method that doesn't need to scan directories - function loadPlugins(loaderConfig) { - if (!vfs.connected) { - vfs.once("connect", loadPlugins.bind(this, loaderConfig)); - return; - } - - if (!loaderConfig.length && !loadFromDisk) - return; - - listAllPackages(function(err, resolved) { - if (err) return console.error(err); - - var extraPackages = {}; - // convert old format from db to the new one - loaderConfig.forEach(function(p) { - if (!extraPackages[p.packageName]) { - var path = "plugins/" + p.packageName; - extraPackages[path] = { - apikey: p.apikey, - packagePath: path, - version: p.version, - name: p.packageName - }; - } - }); - if (!loadFromDisk) { - // filter packages by config instead of loading - // everything from disk - resolved = resolved.filter(function(config) { - if (extraPackages[config.packagePath]) - return true; - - console.warn("[c9.ide.loader] Not loading package " - + config.path + " because it is not installed, " - + "according to the database"); - return false; - }); - } - resolved.filter(function(config) { - if (extraPackages[config.packagePath]) { - _.assign(config, extraPackages[config.packagePath], function(x, y) { return x || y; }); - delete extraPackages[config.packagePath]; - } - }); - Object.keys(extraPackages).forEach(function(packagePath) { - console.warn("[c9.ide.loader] Package " - + packagePath + " should be installed, according " - + "to the database, but was not found on the filesystem. " - + "Try reinstalling it."); - }); - - async.each(resolved, loadPackage, function(err) { - if (err) console.error(err); - }); - }); - } - - /** - * List all packages on disk by scanning `~/.c9` and resolve the - * detected packages by order of override priority: - * - * - Developer plugins in `~/.c9/dev/plugins` have the highest - * priority to allow local development of new functionality without - * the risk of having your changes overwritten by any update - * mechanism. - * - * - Managed plugins in `~/.c9/managed/plugins` are pre-installed and - * updated by the Cloud9 system and have a higher priority that - * possibly outdated or unknown packages installed by the user. - * - * - User-installed in `~/.c9/plugins` plugins installed plugins are - * the default priority have the lowest priority. - * - * @param {Function} callback - * @param {Error=} callback.err - */ - function listAllPackages(callback) { - async.parallel({ - "plugins": async.apply(listPackages, "~/.c9/plugins"), - "managed": async.apply(listPackages, "~/.c9/managed/plugins"), - "dev": async.apply(listPackages, "~/.c9/dev/plugins"), - }, function(err, packages) { - if (err && err.code === "EDISCONNECT") { - c9.once("connect", function() { - listAllPackages(callback); - }); - return; - } - - if (err) return callback(err); - - var resolved = {}; - - // default: ~/.c9/plugins - packages.plugins.forEach(function(config) { - if (!config) return; - resolved[config.name] = config; - }); - - // high: ~/.c9/managed/plugins - packages.managed.forEach(function(config) { - if (!config) return; - resolved[config.name] = config; - }); - - // higher: ~/.c9/dev/plugins - packages.dev.forEach(function(config) { - if (!config) return; - resolved[config.name] = config; - }); - - callback(null, _.values(resolved)); - }); - } - - /** - * List packages in the given directory - * - * @param {String} dirPath Path to the directory to scan - * - * @param {Function} callback - * @param {Error=} callback.err - * @param {Object[]} callback.packages - */ - function listPackages(dirPath, callback) { - fs.readdir(dirPath, function(err, stats) { - if (err && err.code === "ENOENT") - return callback(null, []); - - if (err) - return callback(err); - - async.map(stats, function(stat, done) { - // check for folder or symlink with folder target - if (stat.mime !== "inode/directory" - && (stat.mime === "inode/symlink" && stat.linkStat.mime !== "inode/directory") - ) { - return done(); - } - - // check folers not prefixed with [._] - if (stat.name[0] === "." || stat.name[0] === "_") { - return done(); - } - - // check and load package.json - var config = { - name: stat.name, - path: absolutePath([dirPath, stat.name].join("/")), - packagePath: ["plugins", stat.name].join("/"), - staticPrefix: vfs.url([dirPath, stat.name].join("/")), - }; - done(null, config); - }, callback); - }); - } - - /** - * Load a the `package.json` metadata of the given package - * - * @param {Object} config The package configuration - * - * @param {Function} callback - * @param {Error=} callback.err - * @param {Object} callback.metadata - */ - function loadPackageMetadata(config, callback) { - fs.readfile([config.path, "package.json" ].join("/"), function(metadataStr) { - var metadata; - - try { - metadata = JSON.parse(metadataStr); - } catch (e) { - return callback(e); - } - - callback(null, metadata); - }, function(err) { - callback(err); - }); - } - - /** - * Load a the `__installed__.js` definition of the given package - * - * @param {Object} config The package configuration - * - * @param {Function} callback - * @param {Error=} callback.err - * @param {Array} callback.installed - */ - function loadPackageInstalledJs(config, callback) { - var paths = {}; - paths[config.packagePath] = config.staticPrefix; - - requirejs.config({ paths: paths }); - requirejs.undef(config.packagePath, true); - - require([[config.packagePath, "__installed__"].join("/")], function(installed) { - callback(null, installed); - }, function(err) { - callback(err); - }); - } - - /** - * Load the given package by checking its `__installed__.js` file or - * its `package.json#plugins`, then call `Architect#loadAdditionalPlugins()`. - * - * @param {Object} config The package configuration - * - * @param {Function} callback - * @param {Error=} callback.err - */ - function loadPackage(config, callback) { - loadPackageInstalledJs(config, function addToArchitect(err, installed) { - var plugins = installed; - if (err) { - return callback(err); - // TODO disbled since this doesn't handle bundles and breaks debug=2 - // loadPackageMetadata(config, function(err, metadata) { - // if (err) return callback(err); - // config.metadata = metadata; - // var plugins = _.map(config.metadata.plugins, function(value, key) { - // return [ "plugins", config.name, key ].join("/"); - // }); - // addToArchitect(err, plugins); - // }); - } - - var architectConfig = plugins.map(function(plugin) { - if (typeof plugin == "string") - plugin = { packagePath: plugin }; - - plugin.staticPrefix = config.staticPrefix; - - plugin.packageName = config.name; - plugin.packageMetadata = config.metadata; - plugin.packageDir = config.path; - - plugin.apikey = config.apikey; - plugin.version = config.version; - - return plugin; - }); - - names.push(config.name); - - architect.loadAdditionalPlugins(architectConfig, function(err) { - callback(err); - }); - }); - } - - function absolutePath(fromPath) { - var toPath = fromPath.replace(/^~/, c9.home); - return toPath; - } - - /***** Lifecycle *****/ - - plugin.on("load", function() { - load(); - }); - plugin.on("unload", function() { - }); - - /***** Register and define API *****/ - - /** - * - **/ - plugin.freezePublicAPI({ - /** - * - */ - get architect() { throw new Error(); }, - set architect(v) { architect = v; }, - - /** - * - */ - get plugins() { return names; } - }); - - register(null, { - "plugin.loader": plugin - }); - } -}); diff --git a/plugins/c9.ide.plugins/manager.js b/plugins/c9.ide.plugins/manager.js index 6897162f..266fcc4b 100644 --- a/plugins/c9.ide.plugins/manager.js +++ b/plugins/c9.ide.plugins/manager.js @@ -1,95 +1,30 @@ /*global requirejs*/ define(function(require, exports, module) { main.consumes = [ - "PreferencePanel", "settings", "ui", "util", "Form", "ext", "c9", "apf", - "dialog.alert", "dialog.confirm", "layout", "proc", "menus", "commands", - "dialog.error", "dialog.info", "tree.favorites", "fs", "tree", "vfs", "plugin.debug", - "preferences.experimental" + "app", "ext", "c9", "Plugin", "proc", "fs", "vfs", "dialog.error" ]; - main.provides = ["plugin.manager", "pluginManager"]; + main.provides = ["pluginManager", "plugin.manager", "plugin.debug"]; return main; - /* - - Show Packages to be updated - - Open Plugin Store - - Open Cloud9 in Debug Mode - - List all installed packages - - Filter - - Core packages - - Name - - Version - - Description - - Load Time - - Plugin profile - - Pre-installed - - * - - Custom packages - - * - - - Actions: - - Uninstall - - Report Issue - - Open README - - Open in Cloud9 - - DataProvider.variableHeightRowMixin.call(this) in datagrid constructor - and set node.height - - harutyun [1:11 PM] - or add a custom getItemHeight function like https://github.com/c9/newclient/blob/master/node_modules/ace_tree/demo/demo.js#L63 (edited) - */ function main(options, imports, register) { - var PreferencePanel = imports.PreferencePanel; - var settings = imports.settings; - var layout = imports.layout; - var ui = imports.ui; var c9 = imports.c9; - var menus = imports.menus; var fs = imports.fs; - var commands = imports.commands; var ext = imports.ext; - var tree = imports.tree; var proc = imports.proc; + var Plugin = imports.Plugin; var util = imports.util; - var qs = require("querystring"); - var apf = imports.apf; var vfs = imports.vfs; - var alert = imports["dialog.alert"].show; - var confirm = imports["dialog.confirm"].show; var showError = imports["dialog.error"].show; - var showInfo = imports["dialog.info"].show; - var favs = imports["tree.favorites"]; - var pluginDebug = imports["plugin.debug"]; - var experimental = imports["preferences.experimental"]; + var architectApp = imports.app; - var search = require("../c9.ide.navigate/search"); - var Tree = require("ace_tree/tree"); - var TreeData = require("./managerdp"); var join = require("path").join; var basename = require("path").basename; var dirname = require("path").dirname; var async = require("async"); - + var staticPrefix = options.staticPrefix; - var architect; - var CORE = { - "c9.core": 1, "c9.fs": 1, "c9.ide.preferences": 1, "c9.ide.panels": 1, - "c9.ide.plugins": 1, "c9.ide.login": 1, "c9.vfs.client": 1, - "c9.ide.console": 1, "c9.ide.editors": 1, "c9.ide.dialog.common": 1, - "c9.ide.dialog.file": 1, "c9.ide.dialog.login": 1, "c9.ide.errorhandler": 1, - "c9.ide.help": 1, "c9.ide.keys": 1, "c9.ide.restore": 1, "c9.ide.watcher": 1, - "c9.ide.tree": 1, "c9.ide.info": 1, - "c9.ide.layout.classic": 1, "c9.ide.terminal": 1, "c9.ide.ace": 1, - "c9.ide.clipboard": 1, "c9.nodeapi": 1 - }; - var GROUPS = { - "custom": "Installed Plugins", - "pre": "Pre-installed plugins", - "core": "Core Plugins", - "runtime": "Plugins created runtime" - }; var TEMPLATES = { "plugin.simple": "Empty Plugin", "plugin.default": "Full Plugin", @@ -97,511 +32,36 @@ define(function(require, exports, module) { "plugin.bundle": "Cloud9 Bundle" }; - // @TODO add sorting - /***** Initialization *****/ - var ENABLED = c9.location.indexOf("debug=2") > -1 - || experimental.addExperiment( - "plugin-manager", - options.devel, - "SDK/Plugin Manager" - ); + var plugin = new Plugin(); + var emit = plugin.getEmitter(); - var plugin = new PreferencePanel("Ajax.org", main.consumes, { - caption: "Plugin Manager", - className: "plugins", - form: false, - noscroll: true, - index: 200, - visible: ENABLED, - }); - // var emit = plugin.getEmitter(); - - var model, datagrid, filterbox; - var btnUninstall, btnReport, btnReadme, btnCloud9, btnReload; - - var loaded = false; - function load() { - if (loaded) return false; - loaded = true; - - if (!ENABLED) return; - - // @TODO enable/disable plugins -> move to ext - - // settings.on("read", function(e) { - // updateCommandsFromSettings(); - // }, plugin); - - // commands.on("update", function(){ - // changed = true; - // updateCommandsFromSettings(); - // }, plugin); - - - if (options.devel) { - commands.addCommand({ - name: "reloadLastPlugin", - bindKey: { mac: "F4", win: "F4" }, - hint: "reload plugin last reloaded in plugin manager", - exec: function() { - var name = getLastReloaded(); - if (!name) - return commands.exec("reloadPlugin", null, { panel: plugin }); - reload(name); - } - }, plugin); - commands.addCommand({ - name: "reloadPlugin", - group: "Plugins", - exec: function() { - commands.exec("openpreferences", null, { panel: plugin }); - } - }, plugin); - - menus.addItemByPath("Tools/~", new ui.divider(), 100000, plugin); - menus.addItemByPath("Tools/Developer", null, 100100, plugin); - - menus.addItemByPath("Tools/Developer/Reload Built-in Plugin...", new ui.item({ - command: "reloadPlugin" - }), 1100, plugin); - - menus.addItemByPath("Tools/Developer/Reload Last Plugin", new ui.item({ - command: "reloadLastPlugin", - isAvailable: getLastReloaded - }), 1200, plugin); - } - - menus.addItemByPath("File/New Plugin", null, 210, plugin); - Object.keys(TEMPLATES).forEach(function(name) { - menus.addItemByPath("File/New Plugin/" + TEMPLATES[name], new ui.item({ - onclick: function() { - createNewPlugin(name); - } - }), 210, plugin); - }); - - ext.on("register", function() { - setTimeout(reloadModel); - }); - } - - var drawn; - function draw(e) { - if (drawn) return; - drawn = true; - - model = new TreeData(); - model.emptyMessage = "No plugins found"; - - model.columns = [{ - caption: "Name", - value: "name", - // getText: function(p){ - // return p.name + " (" + p.items.length + ")"; - // }, - width: "250", - type: "tree" - }, { - caption: "Version", - // value: "version", - getText: function(p) { - return p.version || - (p.isPackage - ? p.items.length && p.items[0].version || "" - : ""); - }, - width: "100" - }, { - caption: "Startup Time", - // value: "time", - width: "100", - getText: function(p) { - if (p.time !== undefined) - return (p.time || 0) + "ms"; - - var total = 0; - if (p.isPackage || p.name == "runtime") { - p.items.forEach(function(item) { total += item.time || 0; }); - } - else { - p.items.forEach(function(p) { - p.items && p.items.forEach(function(item) { total += item.time || 0; }); - }); - } - return (p.time = total) + "ms"; - } - }, { - caption: "Enabled", - value: "enabled", - width: "100" - // }, { - // caption: "Package", - // value: "package", // @todo make a link - // width: "100%" - }, { - caption: "Developer", - // value: "developer", - getText: function(p) { - return p.developer || - (p.isPackage - ? p.items.length && p.items[0].developer || "" - : ""); - }, - width: "150" - }]; - - layout.on("eachTheme", function(e) { - var height = parseInt(ui.getStyleRule(".bar-preferences .blackdg .tree-row", "height"), 10) || 24; - model.rowHeightInner = height; - model.rowHeight = height; - - if (e.changed) datagrid.resize(true); - }); - - reloadModel(); - - // type: "custom", - // title: "Introduction", - // position: 1, - // node: intro = new ui.bar({ - // height: 149, - // "class" : "intro", - // style: "padding:12px;position:relative;" - // }) - - // intro.$int.innerHTML = - // '

Keybindings

Change these settings to configure ' - // + 'how Cloud9 responds to your keyboard commands.

' - // + '

You can also manually edit your keymap file.

' - // + '

Hint: Double click on the keystroke cell in the table below to change the keybinding.

'; - - // intro.$int.querySelector("a").onclick = function(){ editUserKeys(); }; - - var hbox = new ui.hbox({ - htmlNode: e.html, - padding: 5, - edge: "10 10 0 10", - childNodes: [ - filterbox = new apf.codebox({ - realtime: true, - skin: "codebox", - "class": "dark", - clearbutton: true, - focusselect: true, - height: 27, - width: 250, - singleline: true, - "initial-message": "Search installed plugins" - }), - new ui.filler({}), - btnUninstall = new ui.button({ - skin: "btn-default-css3", - caption: "Uninstall", - class: "btn-red", - onclick: function() { - var item = datagrid.selection.getCursor(); - if (item.isPackage) - uninstall(item.name, function() {}); - else if (item.enabled == "true") - disable(item.name, function() {}); - else - enable(item.name, function() {}); - } - }), - btnReport = new ui.button({ - skin: "btn-default-css3", - caption: "Report Issue" - }), - btnReadme = new ui.button({ - skin: "btn-default-css3", - caption: "Open README" - }), - btnCloud9 = new ui.button({ - skin: "btn-default-css3", - caption: "Open in Cloud9" - }), - btnReload = new ui.button({ - skin: "btn-default-css3", - caption: "Reload", - onclick: function() { - var item = datagrid.selection.getCursor(); - if (item.enabled && item.name) - reload(item.name); - } - }) - ] - }); - - var div = e.html.appendChild(document.createElement("div")); - div.style.position = "absolute"; - div.style.left = "10px"; - div.style.right = "10px"; - div.style.bottom = "10px"; - div.style.top = "50px"; - - datagrid = new Tree(div); - datagrid.setTheme({ cssClass: "blackdg" }); - datagrid.setDataProvider(model); - - layout.on("resize", function() { datagrid.resize(); }, plugin); - - function setTheme(e) { - filterbox.setAttribute("class", - e.theme.indexOf("dark") > -1 ? "dark" : ""); - } - layout.on("themeChange", setTheme); - setTheme({ theme: settings.get("user/general/@skin") }); - - filterbox.ace.commands.addCommands([ - { - bindKey: "Enter", - exec: function() { } - }, { - bindKey: "Esc", - exec: function(ace) { ace.setValue(""); } - } - ]); - - filterbox.ace.on("input", function(e) { - applyFilter(); - }); - - // when tab is restored datagrids size might be wrong - // todo: remove this when apf bug is fixed - datagrid.once("mousemove", function() { - datagrid.resize(true); - }); - - datagrid.on("changeSelection", function(e) { - var item = datagrid.selection.getCursor(); - - if (item.isGroup) { - btnUninstall.disable(); - btnReport.disable(); - btnReadme.disable(); - btnCloud9.disable(); - btnReload.disable(); - } - else { - if (item.isPackage) { - btnUninstall.setCaption("Uninstall"); - btnUninstall.setAttribute("class", "btn-red"); - } - else { - btnUninstall.setCaption(item.enabled == "true" ? "Disable" : "Enable"); - btnUninstall.setAttribute("class", item.enabled == "true" ? "btn-red" : "btn-green"); - } - - if (CORE[item.name] || item.parent.parent && item.parent.parent.isType == "core") { - btnUninstall.disable(); - } else { - btnUninstall.enable(); - } - - if (item.isPackage || CORE[item.name] || item.parent.parent && item.parent.parent.isType == "core") { - btnReload.disable(); - } else { - btnReload.enable(); - } - - btnReport.enable(); - btnReadme.enable(); - btnCloud9.enable(); - } - }); - } + var disabledPlugins = Object.create(null); + var packages = Object.create(null); /***** Methods *****/ - - function reloadModel() { - if (!model) return; - - var groups = {}; - var packages = {}; - var root = []; - - ["custom", "pre", "core", "runtime"].forEach(function(name) { - root.push(groups[name] = { - items: [], - isOpen: name != "runtime", - className: "group", - isGroup: true, - isType: name, - noSelect: true, - name: GROUPS[name] + + function readAvailablePlugins(callback) { + fs.readdir("~/.c9/plugins", function(err, list) { + if (err) return callback && callback(err); + + var available = []; + list.forEach(function(stat) { + var name = stat.name; + if (!/(directory|folder)$/.test(stat.mime)) return; + if (!/[._]/.test(name[0])) available.push(name); }); + callback && callback(null, available); }); - - var lut = ext.named; - - ext.plugins.forEach(function(plugin) { - var info = architect.pluginToPackage[plugin.name]; - var packageName = info && info.package || "runtime"; - - var groupName; - if (CORE[packageName]) groupName = "core"; - else if (info && info.isAdditionalMode) groupName = "custom"; - else groupName = "pre"; - - var package; - if (packageName == "runtime") { - package = groups.runtime; - } - else { - package = packages[packageName]; - if (!package) - groups[groupName].items.push(package = packages[packageName] = { - items: [], - isPackage: true, - className: "package", - parent: groups[groupName], - name: packageName - }); - } - - package.items.push({ - name: plugin.name, - enabled: lut[plugin.name].loaded ? "true" : "false", - time: plugin.time, - version: info && info.version || "N/A", - parent: package, - package: packageName, - developer: plugin.developer == "Ajax.org" - ? "Cloud9" - : plugin.developer - }); - }); - - model.cachedRoot = { items: root }; - applyFilter(); - } - - function applyFilter() { - model.keyword = filterbox && filterbox.getValue(); - - if (!model.keyword) { - model.reKeyword = null; - model.setRoot(model.cachedRoot); - - // model.isOpen = function(node){ return node.isOpen; } - } - else { - model.reKeyword = new RegExp("(" - + util.escapeRegExp(model.keyword) + ")", 'i'); - var root = search.treeSearch(model.cachedRoot.items, model.keyword, true); - model.setRoot(root); - - // model.isOpen = function(node){ return true; }; - } - } - - function uninstall(name) { - btnUninstall.setAttribute("caption", "..."); - btnUninstall.disable(); - - // @TODO first disable the plugin - - proc.spawn("c9", { args: ["uninstall", name]}, function(err, p) { - p.stdout.on("data", function(c) { - - }); - p.stderr.on("data", function(c) { - - }); - p.on("exit", function(code) { - if (code) { - return alert("Could not uninstall plugin", - "Could not uninstall plugin", - "Could not uninstall plugin"); - } - - btnUninstall.setAttribute("caption", "Uninstall"); - btnUninstall.enable(); - }); - }); - } - - function enable(name) { - try { ext.enablePlugin(name); } - catch (e) { - alert("Could not disable plugin", - "Got an error when disabling plugin: " + name, - e.message); - return false; - } - - reloadModel(); - } - - function disable(name, callback) { - var deps = ext.getDependencies(name); - var plugins = ext.named; - - for (var i = 0; i < deps.length; i++) { - ext.getDependencies(deps[i]).forEach(function(name) { - if (deps.indexOf(name) == -1) - deps.push(name); - }); - } - - if (deps.length) { - confirm("Found " + deps.length + " plugins that depend on this plugin.", - "Would you like to disable all the plugins that depend on '" + name + "'?", - "These plugins would also be disabled: " + deps.join(", "), - // Yes - function() { - if (deps.reverse().every(function(name) { - console.log("Disabling", name); - return !recurDisable(name); - })) { - disable(name); - reloadModel(); - } - }, - // No - function() { - callback(new Error("User Cancelled")); - }); - } - else { - var e = disable(name); - if (!e) reloadModel(); - callback(e); - } - - function recurDisable(name) { - var deps = ext.getDependencies(name); - - if (deps.length) { - if (!deps.every(function(name) { - return !recurDisable(name); - })) return false; - } - - return disable(name); - } - - function disable(name) { - if (!plugins[name].loaded) return; - - try { ext.disablePlugin(name); } - catch (e) { - alert("Could not disable plugin", - "Got an error when disabling plugin: " + name, - e.message); - return e; - } - } } function createNewPlugin(template) { if (!template) template = "c9.ide.default"; - - var url = staticPrefix + "/" + join("templates", template + ".tar.gz"); + + var tarSourcePath = join("templates", template + ".tar.gz"); + var url = staticPrefix + "/" + tarSourcePath; if (!url.match(/^http/)) url = location.origin + url; @@ -633,8 +93,16 @@ define(function(require, exports, module) { args: ["-c", [ // using mkdirp since "--create-dirs" is broken on windows "mkdir", "-p", util.escapeShell(dirname(tarPathAbsolute)), ";", - "curl", "-L", util.escapeShell(url), "-o", util.escapeShell(tarPathAbsolute)].join(" ") - ] + ].concat( + c9.sourceDir + ? [ "cp", util.escapeShell(c9.sourceDir + "/plugins/c9.ide.plugins/" + tarSourcePath), + util.escapeShell(tarPathAbsolute) ] + : [ "curl", "-L", + (architectApp.services.onlinedev_helper ? "-k" : ""), // ignore certificate errors in dev mode + util.escapeShell(url), + "-o", util.escapeShell(tarPathAbsolute) + ].filter(Boolean) + ).join(" ")] }, function(err, stderr, stdout) { if (err) return handleError(err); @@ -654,12 +122,19 @@ define(function(require, exports, module) { // Remove .tar.gz fs.unlink(tarPath, function() { + // using this to allow reloading the tree + var tree = architectApp.services["tree"]; + var favs = architectApp.services["tree.favorites"]; // Add plugin to favorites favs.addFavorite(dirname(pluginsDir), "plugins"); // Select and expand the folder of the plugin tree.expandAndSelect(path); + + readAvailablePlugins(function(e) { + emit("change"); + }); }); }); }); @@ -667,40 +142,129 @@ define(function(require, exports, module) { }); } - function reload(name) { - showReloadTip(name); - - var href = document.location.href.replace(/[?&]reload=[^&]+/, ""); - href += (href.match(/\?/) ? "&" : "?") + "reload=" + name; - window.history.replaceState(window.history.state, null, href); - - for (var plugin in architect.lut) { - if (architect.lut[plugin].provides.indexOf(name) < 0) - continue; - - pluginDebug.reloadPackage(plugin); - return; - } - } - - function showReloadTip(name) { - if (options.devel) { - var key = commands.getHotkey("reloadLastPlugin"); - if (commands.platform == "mac") - key = apf.hotkeys.toMacNotation(key); - if (!getLastReloaded()) { - showInfo("Reloaded " + name + ". Press " + key + " to reload again.", 3000); - return; + function reload(nodes, mode) { + var reloadLast = []; + nodes.forEach(function(node) { + var id; + if (node.packageConfig) { + var config = node.packageConfig; + if (!mode) + unloadPackage(config.name); + if (mode != false) + loadPackage(config.filePath || config.url); + id = config.name; } + else { + if (!mode) + unloadPlugins({ path: node.path }); + if (mode != false) + loadPlugins({ path: node.path }); + id = node.path; + } + + reloadLast.push(id); + if (mode == false) + disabledPlugins[id] = true; + else + delete disabledPlugins[id]; + }); + return reloadLast; + } + + function checkPluginsWithMissingDependencies() { + var services = architectApp.services; + var plugins = []; + getAllPlugins().forEach(function(p) { + if (!p.provides.length) return; + var packagePath = p.packagePath; + var isDisabled = p.provides.every(function(name) { + if (!services[name]) return true; + if (!services[name].loaded && typeof services[name].load == "function") + return true; + }); + if (isDisabled && packagePath) { + var packageName = packagePath.split("/")[1]; + if (disabledPlugins[packageName]) return; + while (packagePath && !disabledPlugins[packagePath]) { + var i = packagePath.lastIndexOf("/"); + packagePath = packagePath.slice(0, i > 0 ? i : 0); + } + if (!packagePath) plugins.push(p); + } + }); + if (!plugins.length) return; + architectApp.loadAdditionalPlugins(plugins, function(err) { + if (err) return showError(err); + }); + } + + function getAllPlugins(includeDisabled) { + var config = architectApp.config; + Object.keys(packages).forEach(function(n) { + if (packages[n] && packages[n].c9 && packages[n].c9.plugins) + config = config.concat(packages[n].c9.plugins); + }); + return includeDisabled ? config : config.filter(function(x) { + return x.consumes && x.provides; + }); + } + + function addAllDependents(plugins) { + var config = getAllPlugins(); + var level = 0; + do { + level++; + var changed = false; + var packageNames = Object.keys(plugins); + config.forEach(function(x) { + packageNames.forEach(function(p) { + if (x.consumes.indexOf(p) != -1) { + x.provides.forEach(function(name) { + if (plugins[name] == null) { + changed = true; + plugins[name] = level; + } + }); + } + }); + }); + } while (changed); + return plugins; + } + + function addAllProviders(plugins) { + var serviceToPlugin = architectApp.serviceToPlugin; + var level = 0; + do { + level--; + var changed = false; + var packageNames = Object.keys(plugins); + packageNames.forEach(function(p) { + var service = serviceToPlugin[p]; + if (service && service.consumes) { + service.consumes.forEach(function(name) { + if (plugins[name] == null) { + changed = true; + plugins[name] = level; + } + }); + } + }); + } while (changed); + return plugins; + } + + function unloadPackage(options, callback) { + if (Array.isArray(options)) + return async.map(options, unloadPackage, callback || function() {}); + var name = typeof options == "object" ? options.name : options; + if (packages[name]) { + packages[name].enabled = false; + unloadPlugins(packages[name].path); + emit("disablePackage"); } - showInfo("Reloaded " + name + ".", 1000); } - function getLastReloaded() { - return qs.parse(document.location.search.substr(1)).reload; - } - - var packages = {}; function loadPackage(options, callback) { if (Array.isArray(options)) return async.map(options, loadPackage, callback || function() {}); @@ -715,8 +279,10 @@ define(function(require, exports, module) { } } + var url = options.url; + if (!options.url && options.path) - options.url = vfs.vfsUrl(options.path); + options.url = vfs.url(options.path); var parts = options.url.split("/"); var root = parts.pop(); @@ -729,7 +295,7 @@ define(function(require, exports, module) { if (!options.name || options.name == "json") options.name = parts[parts.length - 1]; // try parent folder name - if (/^(.?build|master)/.test(options.name)) + if (/^(.?build|master|c9build)/.test(options.name)) options.name = parts[parts.length - 2]; // remove version from the name options.name = options.name.replace(/@.*$/, ""); @@ -744,6 +310,10 @@ define(function(require, exports, module) { var id = options.rootDir + "/" + name; var pathMappings = {}; + if (packages[name]) packages[name].loading = true; + + unloadPlugins("plugins/" + options.name); + pathMappings[id] = options.url; requirejs.config({ paths: pathMappings }); requirejs.undef(id + "/", true); @@ -751,6 +321,12 @@ define(function(require, exports, module) { if (/\.js$/.test(root)) { require([options.url + "/" + root], function(json) { json = json || require(id + "/" + options.packageName); + if (!json) { + var err = new Error("Didn't provide " + id + "/" + options.packageName); + return addError("Error loading plugin", err); + } + if (json.name && json.name != name) + name = json.name; getPluginsFromPackage(json, callback); }, function(err) { addError("Error loading plugin", err); @@ -765,6 +341,8 @@ define(function(require, exports, module) { return addError("Error parsing package.json", e); } json.fromVfs = true; + // handle the old format + if (!json.c9) loadBundleFiles(json, options); getPluginsFromPackage(json, callback); }); } @@ -788,10 +366,11 @@ define(function(require, exports, module) { if (!packages[name]) packages[name] = {}; packages[name].filePath = options.path; - packages[name].url = options.url; + packages[name].url = url; packages[name].__error = new Error(message + "\n" + err.message); + packages[name].loading = false; - reloadModel(); + emit("change"); callback && callback(err); } @@ -799,7 +378,7 @@ define(function(require, exports, module) { function getPluginsFromPackage(json, callback) { var plugins = []; if (json.name != name) - name = json.name; + json.name = name; var unhandledPlugins = json.c9 && json.c9.plugins || json.plugins; if (unhandledPlugins) { Object.keys(unhandledPlugins).forEach(function(name) { @@ -815,7 +394,7 @@ define(function(require, exports, module) { packages[json.name] = json; json.filePath = options.path; - json.url = options.url; + json.url = url; if (!json.c9) json.c9 = {}; @@ -824,49 +403,287 @@ define(function(require, exports, module) { json.enabled = true; json.path = id; - loadPlugins(plugins, callback); + emit("enablePackage", json); + loadPlugins(plugins, function(err, result) { + if (err) return addError("Error loading plugins", err); + if (packages[name]) + packages[name].loading = false; + emit("change"); + callback && callback(err, result); + }); + } + + function loadBundleFiles(json, options, callback) { + var cwd = dirname(options.path); + var resourceHolder = new Plugin(); + fs.readdir(cwd, function(err, files) { + if (err) return callback && callback(err); + function forEachFile(dir, fn) { + fs.readdir(dir, function(err, files) { + if (err) return callback && callback(err); + files.forEach(function(stat) { + fs.readFile(dir + "/" + stat.name, function(err, value) { + if (err) return callback && callback(err); + fn(stat.name, value); + }); + }); + }); + } + function parseHeader(data, filename) { + var firstLine = data.split("\n", 1)[0].replace(/\/\*|\*\//g, "").trim(); + var info = {}; + firstLine.split(";").forEach(function(n) { + var key = n.split(":"); + if (key.length != 2) + return console.error("Ignoring invalid key " + n + " in " + filename); + info[key[0].trim()] = key[1].trim(); + }); + info.data = firstLine; + return info; + } + function addResource(type) { + forEachFile(cwd + "/" + type, function(filename, data) { + addStaticPlugin(type, options.name, filename, data, plugin); + + }); + } + function addMode(type) { + forEachFile(cwd + "/modes", function(filename, data) { + if (/(?:_highlight_rules|_test|_worker|_fold|_behaviou?r)\.js$/.test(filename)) + return; + if (!/\.js$/.test(filename)) + return; + var info = parseHeader(data, cwd + "/modes/" + filename); + + if (!info.caption) info.caption = filename; + + info.type = "modes"; + info.filename = filename; + addStaticPlugin(type, options.name, filename, data, plugin); + }); + } + var handlers = { + templates: addResource, + snippets: addResource, + builders: addResource, + keymaps: addResource, + outline: addResource, + runners: addResource, + themes: addResource, + modes: addMode, + }; + files.forEach(function(stat) { + var type = stat.name; + if (handlers.hasOwnProperty(type)) + handlers[type](type); + }); + + var name = options.name + ".bundle"; + var bundle = { + packagePath: id + "/" + name, + consumes: [], + provides: [name], + setup: function(imports, options, register) { + var ret = {}; + ret[name] = resourceHolder; + register(null, ret); + } + }; + json.c9.plugins.push(bundle); + architectApp.loadAdditionalPlugins([bundle], function() {}); + }); } } function loadPlugins(plugins, callback) { - architect.loadAdditionalPlugins(plugins, function(err) { + if (!Array.isArray(plugins)) { + options = plugins; + plugins = []; + Object.keys(getServiceNamesByPath(options)).forEach(function(n) { + var plugin = architectApp.serviceToPlugin[n]; + if (plugin && plugin.packagePath) { + unloadPluginConfig(plugin); + plugins.push(plugin); + } + }); + } + + architectApp.loadAdditionalPlugins(plugins, function(err) { + setTimeout(checkPluginsWithMissingDependencies); callback && callback && callback(err); }); } + + function getServiceNamesByPath(options) { + var toUnload = Object.create(null); + var config = getAllPlugins(); + function addPath(path) { + config.forEach(function(p) { + if (!p.packagePath) return; + if (p.packagePath.startsWith(path)) { + p.provides.forEach(function(name) { + toUnload[name] = 0; + }); + } + }); + } + if (!options) { + return toUnload; + } + if (typeof options == "string") { + addPath(options); + } + if (options.path) { + addPath(options.path); + } + if (options.paths) { + options.path.forEach(addPath); + } + if (options.services) { + options.services.forEach(function(name) { + toUnload[name] = 0; + }); + } + return toUnload; + } + + function unloadPlugins(options, callback) { + var toUnload = getServiceNamesByPath(options); + addAllDependents(toUnload); + + var services = architectApp.services; + var serviceToPlugin = architectApp.serviceToPlugin; + Object.keys(toUnload).forEach(function(name) { + recurUnload(name); + }); + + function recurUnload(name) { + var service = services[name]; + + if (!service || !service.loaded) + return; + + // Find all the dependencies + var deps = ext.getDependents(service.name); + + // Unload all the dependencies (and their deps) + deps.forEach(function(name) { + recurUnload(name); + }); + + console.log(name); + + // Unload plugin + service.unload(); + + var pluginConfig = serviceToPlugin[name]; + if (pluginConfig && toUnload[name] == 0) + pluginConfig.__userDisabled = true; + } + + emit("change"); + } + + function unloadPluginConfig(plugin) { + var url = requirejs.toUrl(plugin.packagePath, ".js"); + if (!define.fetchedUrls[url]) return false; + + delete plugin.provides; + delete plugin.consumes; + delete plugin.setup; + + requirejs.undef(plugin.packagePath); + return true; + } + + // TODO optimize this + function addStaticPlugin(type, pluginName, filename, data, plugin) { + var services = architectApp.services; + var path = "plugins/" + pluginName + "/" + + (type == "installer" ? "" : type + "/") + + filename.replace(/\.js$/, ""); + + switch (type) { + case "builders": + data = util.safeParseJson(data, function() {}); + if (!data) return; + if (!services.build.addBuilder) return; + + services.build.addBuilder(filename, data, plugin); + break; + case "keymaps": + data = util.safeParseJson(data, function() {}); + if (!data) return; + if (!services["preferences.keybindings"].addCustomKeymap) return; + + services["preferences.keybindings"].addCustomKeymap(filename, data, plugin); + break; + case "modes": + if (!services.ace) return; + var mode = {}; + var firstLine = data.split("\n", 1)[0].replace(/\/\*|\*\//g, "").trim(); + firstLine.split(";").forEach(function(n) { + var info = n.split(":"); + if (info.length != 2) return; + mode[info[0].trim()] = info[1].trim(); + }); + + services.ace.defineSyntax({ + name: path, + caption: mode.caption || filename, + extensions: (mode.extensions || "").trim() + .replace(/\s*,\s*/g, "|").replace(/(^|\|)\./g, "$1") + }); + break; + case "outline": + if (!data) return; + if (!services.outline.addOutlinePlugin) return; + + services.outline.addOutlinePlugin(path, data, plugin); + break; + case "runners": + data = util.safeParseJson(data, function() {}); + if (!data) return; + + services.run.addRunner(data.caption || filename, data, plugin); + break; + case "snippets": + services["language.complete"].addSnippet(data, plugin); + break; + case "themes": + services.ace.addTheme(data, plugin); + break; + case "templates": + services.newresource.addFileTemplate(data, plugin); + break; + case "installer": + if (data) { + services.installer.createSession(pluginName, data, function(v, o) { + require([path], function(fn) { + fn(v, o); + }); + }); + } + else { + require([path], function(fn) { + services.installer.createSession(pluginName, fn.version, function(v, o) { + fn(v, o); + }); + }); + } + default: + console.error("Unsupported type", type); + } + } + /***** Lifecycle *****/ plugin.on("load", function() { - load(); - }); - plugin.on("draw", function(e) { - draw(e); - }); - plugin.on("activate", function(e) { - datagrid && datagrid.resize(); - }); - plugin.on("resize", function(e) { - datagrid && datagrid.resize(); - }); - plugin.on("enable", function() { - - }); - plugin.on("disable", function() { - }); plugin.on("unload", function() { - loaded = false; - drawn = false; - - architect = null; - model = null; - datagrid = null; - filterbox = null; - btnUninstall = null; - btnReport = null; - btnReadme = null; - btnCloud9 = null; - btnReload = null; + disabledPlugins = null; + plugin = null; }); /***** Register and define API *****/ @@ -878,19 +695,33 @@ define(function(require, exports, module) { /** * */ - get architect() { throw new Error(); }, - set architect(v) { - architect = v; - architect.on("ready-additional", function() { - reloadModel(); - }); - }, - + readAvailablePlugins: readAvailablePlugins, + /** * */ createNewPlugin: createNewPlugin, - + + /** + * + */ + getAllPlugins: getAllPlugins, + + /** + * + */ + addAllDependents: addAllDependents, + + /** + * + */ + addAllProviders: addAllProviders, + + /** + * + */ + unloadPackage: unloadPackage, + /** * */ @@ -899,27 +730,45 @@ define(function(require, exports, module) { /** * */ - uninstall: uninstall, - + loadPlugins: loadPlugins, + /** * */ - enable: enable, - + reload: reload, + /** * */ - disable: disable, - + getServiceNamesByPath: getServiceNamesByPath, + /** * */ - reload: reload + unloadPlugins: unloadPlugins, + + /** + * + */ + unloadPluginConfig: unloadPluginConfig, + + /** + * + */ + addStaticPlugin: addStaticPlugin, + + + get packages() { return packages; }, + get disabledPlugins() { return disabledPlugins; }, }); + var shim = new Plugin(); + shim.addStaticPlugin = addStaticPlugin; + register(null, { "pluginManager": plugin, - "plugin.manager": plugin + "plugin.manager": shim, + "plugin.debug": shim, }); } }); diff --git a/plugins/c9.ide.plugins/managerdp.js b/plugins/c9.ide.plugins/managerdp.js index cf695978..eb638e7d 100644 --- a/plugins/c9.ide.plugins/managerdp.js +++ b/plugins/c9.ide.plugins/managerdp.js @@ -9,32 +9,6 @@ define(function(require, exports, module) { oop.inherits(DataProvider, TreeData); (function() { - - this.getChildren = function(node) { - var children = node.items; - var ch = children && children[0] && children[0]; - if (ch) { - var d = (node.$depth + 1) || 0; - children.forEach(function(n) { - n.$depth = d; - n.parent = node; - }); - } - - if (this.$sortNodes && !node.$sorted) { - children && this.sort(children); - } - return children; - }; - - // this.getRowIndent = function(p){ - // return !p.className ? 2 : p.className == "package" ? 1 : 0; - // } - - this.hasChildren = function(node) { - return node.items && node.items.length; - }; - this.getCaptionHTML = function(node) { if (!node.name) return ""; return node.name.replace(this.reKeyword, "$1"); diff --git a/plugins/c9.ide.plugins/packages.js b/plugins/c9.ide.plugins/packages.js deleted file mode 100644 index 2cfd588b..00000000 --- a/plugins/c9.ide.plugins/packages.js +++ /dev/null @@ -1,202 +0,0 @@ -define(function(require, exports, module) { - main.consumes = [ - "Editor", "editors", "ui", "commands", "menus", "layout", - "tabManager", "util", "settings", "api", "c9", - "preferences.experimental" - ]; - main.provides = ["plugin.packages"]; - return main; - - function main(options, imports, register) { - var Editor = imports.Editor; - var editors = imports.editors; - var tabs = imports.tabManager; - var commands = imports.commands; - var settings = imports.settings; - var api = imports.api; - var c9 = imports.c9; - var menus = imports.menus; - var util = imports.util; - var layout = imports.layout; - var ui = imports.ui; - var experimental = imports["preferences.experimental"]; - - /***** Initialization *****/ - - var extensions = []; - var packages = {}; - - var handle = editors.register("plugin.packages", "Package Browser", - PackageBrowser, extensions); - var emit = handle.getEmitter(); - emit.setMaxListeners(1000); - - var DEBUG_MODE = c9.location.indexOf("debug=2") > -1; - var HASSDK = DEBUG_MODE || experimental.addExperiment("sdk", false, "SDK/Load Custom Plugins"); - - function focusOpenPackages() { - var pages = tabs.getTabs(); - for (var i = 0, tab = pages[i]; tab; tab = pages[i++]) { - if (tab.editorType == "plugin.packages") { - tabs.focusTab(tab); - return true; - } - } - } - - handle.on("load", function() { - if (!HASSDK) return; - - settings.on("read", function() { - settings.setDefaults("user/general", [["animateui", true]]); - }); - - commands.addCommand({ - name: "openpackagebrowser", - hint: "open the package browser", - group: "General", - // bindKey: { mac: "Command-,", win: "Ctrl-," }, - exec: function () { - var tab = tabs.focussedTab; - if (tab && tab.editor.type == "plugin.packages") { - tab.close(); - return; - } - if (focusOpenPackages()) - return; - - tabs.open({ - editorType: "plugin.packages", - active: true - }, function() {}); - } - }, handle); - - // menus.addItemByPath("Cloud9/~", new ui.divider(), 1000, handle); - // menus.addItemByPath("Cloud9/Package Browser", new ui.item({ - // command: "openpackagebrowser" - // }), 1100, handle); - }); - - /***** Methods *****/ - - function installPlugin(name, target, callback) { - var config = packages[name]; - if (!config) - return callback(new Error("Could not find plugin: " + name)); - - if (target != "project" && target != "user") - return callback(new Error("Invalid installation target: " + target)); - - api[target].put("install/" + name, function(err, state) { - callback(err); - }); - } - - function uninstallPlugin(name, target, callback) { - var config = packages[name]; - if (!config) - return callback(new Error("Could not find plugin: " + name)); - - if (target != "project" && target != "user") - return callback(new Error("Invalid installation target: " + target)); - - api[target].put("uninstall/" + name, function(err, state) { - callback(err); - }); - } - - /***** Register and define API *****/ - - /** - * - * @extends Plugin - * @singleton - */ - handle.freezePublicAPI({ - /** - * - */ - installPlugin: installPlugin, - - /** - * - */ - uninstallPlugin: uninstallPlugin - }); - - /***** Editor *****/ - - function PackageBrowser() { - var plugin = new Editor("Ajax.org", main.consumes, extensions); - //var emit = plugin.getEmitter(); - var tab, iframe; - - plugin.on("resize", function(e) { - emit("resize", e); - }); - - plugin.on("draw", function(e) { - tab = e.tab; - var htmlNode = e.htmlNode; - - htmlNode.style.paddingTop = 0; - - iframe = htmlNode.appendChild(document.createElement("iframe")); - iframe.style.width = "100%"; - iframe.style.height = "100%"; - iframe.style.border = 0; - iframe.style.backgroundColor = "#fbfbfb"; - - iframe.src = location.origin.replace("ide.", "") + "/profile/packages?nobar=1&pid=" + c9.projectId; - }); - - plugin.on("getState", function(e) { - }); - plugin.on("setState", function(e) { - }); - - /***** Lifecycle *****/ - - plugin.on("documentLoad", function(e) { - var doc = e.doc; - doc.title = "Package Browser"; - - function setTheme() { - var bg = "#fbfbfb"; - doc.tab.backgroundColor = bg; - - if (util.shadeColor(bg, 1).isLight) - doc.tab.classList.remove("dark"); - else - doc.tab.classList.add("dark"); - } - - layout.on("themeChange", setTheme, e.doc); - setTheme(); - }); - - plugin.on("documentActivate", function(e) { - - }); - - /***** Register and define API *****/ - - /** - * - * @extends Editor - */ - plugin.freezePublicAPI({ - - }); - - plugin.load(null, "plugin.packages"); - - return plugin; - } - - register(null, { - "plugin.packages": handle - }); - } -}); \ No newline at end of file diff --git a/plugins/c9.ide.plugins/style.css b/plugins/c9.ide.plugins/style.css index ce014210..7436e995 100644 --- a/plugins/c9.ide.plugins/style.css +++ b/plugins/c9.ide.plugins/style.css @@ -4,3 +4,17 @@ .bk-window.dialog-updater .bk-win-footer { display: none; } +.serviceButton { + cursor: default; +} +.serviceButton:hover, .serviceButtonActive { + color: @preferences-intro-link-color; + text-decoration : underline; +} +.tree-row.load-error { + color: #f99145; +} +h1.pluginManagerHeader:after { + background: none; + content: "-"; +} \ No newline at end of file diff --git a/plugins/c9.ide.plugins/updater-npm.js b/plugins/c9.ide.plugins/updater-npm.js deleted file mode 100644 index 13d89abc..00000000 --- a/plugins/c9.ide.plugins/updater-npm.js +++ /dev/null @@ -1,417 +0,0 @@ -define(function(require, exports, module) { - "use strict"; - - main.consumes = [ - "Plugin", - "c9", "proc", "Dialog", "ui", - ]; - main.provides = ["plugin.updater.npm"]; - return main; - - function main(options, imports, register) { - var Plugin = imports["Plugin"]; - var c9 = imports["c9"]; - var proc = imports["proc"]; - var Dialog = imports["Dialog"]; - var ui = imports["ui"]; - - var async = require("async"); - var path = require("path"); - var semverCompare = require("semver-compare"); - - var NPM_MIN_VERSION = "2.6.0"; - - /***** Initialization *****/ - - var npmBin = options.npmBin || "/home/ubuntu/.nvm/nvm-exec"; - var managedPath = options.managedPath || "/home/ubuntu/.c9/managed"; - - var managedRcPath = [managedPath, ".npmrc"].join("/"); - var managedNpmPath = [managedPath, "npm"].join("/"); - var managedEtcPath = [managedNpmPath, "etc"].join("/"); - var managedCachePath = [managedPath, "npm", "cache"].join("/"); - var managedPluginsPath = [managedPath, "plugins"].join("/"); - var managedModulesPath = [managedPath, "node_modules"].join("/"); - - var plugin = new Plugin("Ajax.org", main.consumes); - - function load() { - ui.insertCss(require("text!./style.css"), false, plugin); - - var pkgs = options.packages; - - if (!pkgs) { - console.warn("[plugin.updater.npm] no managed packages configured, not loading."); - return; - } - - // TODO: DRY error handling - - fsMkdirs([ managedPath, managedEtcPath, managedModulesPath, managedPluginsPath ], function(err) { - if (err) { - console.error("[plugin.updater.npm]", err); - showErrorDialog(err); - return; - } - - fsWriteNpmrc(function(err) { - if (err) { - console.error("[plugin.updater.npm]", err); - showErrorDialog(err); - return; - } - - npmCheckVersion(function(err) { - if (err) { - console.error("[plugin.updater.npm]", err); - showErrorDialog(err); - return; - } - - // TODO: clean up the flow for detecting and installing missing - // packages. the nested functions are messy. - - async.filter(pkgs, function(pkg, done) { - npmExplorePath(pkg, function(err, path) { - var isMissing = !!err; - - if (isMissing) - debug("missing package:", pkg, err); - - done(isMissing); - }); - }, function(missing) { - if (missing.length) { - console.info("[plugin.updater.npm] Installing missing plugins:", missing); - showUpdateDialog(); - - npmInstall(missing, function(err) { - if (err) { - console.error("[plugin.updater.npm]", err); - showErrorDialog(err); - return; - } - - buildLinks(pkgs, function(err) { - if (err) { - console.error("[plugin.updater.npm]", err); - showErrorDialog(err); - return; - } - - // reload browser - window.location.reload(); - }); - }); - } - else { - npmOutdated(pkgs, function(err, outdated) { - if (err) { - console.error("[plugin.updater.npm]", err); - showErrorDialog(err); - return; - } - - if (!outdated) { - debug("Plugins up-to-date."); - return; - } - - console.info("[plugin.updater.npm] Updating outdated plugins:", outdated); - showUpdateDialog(); - - buildLinks(pkgs, function(err) { - if (err) { - console.error("[plugin.updater.npm]", err); - showErrorDialog(err); - return; - } - - npmUpdate(outdated, function(err) { - if (err) { - console.error("[plugin.updater.npm]", err); - alert("[plugin.updater.npm] Error: " + err.message); - } - - // reload browser - window.location.reload(); - }); - }); - }); - } - }); - }); - }); - }); - } - - function unload() { - } - - /* -- Helper Functions ----- */ - - function debug(format) { - if (c9.debug) { - var util = require("util"); - console.info(util.format.apply(null, - Array.prototype.slice.call(arguments) - )); - } - } - - /* -- npm Management ----- */ - - /** - * Check that the installed npm version matches the NPM_MIN_VERSION - * required - * - * @param {Function} callback - * @param {Error=} callback.err An error if the version is lower than required - */ - function npmCheckVersion(callback) { - npmExec("npm", ["-v"], function(err, stdout, stderr) { - if (err) return callback(err, stdout, stderr); - - var version = stdout; - - debug("npm version", version); - - if (semverCompare(version, NPM_MIN_VERSION) === -1) { - var error = new Error("npm version " + NPM_MIN_VERSION + " or greater required"); - return callback(error); - } - - callback(); - }); - } - - function npmExec(command, args, callback) { - debug(npmBin, { args: [ "npm", command ].concat(args) }); - - proc.execFile(npmBin, { - args: [ "npm", command ].concat(args), - cwd: managedPath, - env: { - "npm_config_production": "true", - "npm_config_depth": 0, - "npm_config_userconfig": "/dev/null", - "npm_config_prefix": managedNpmPath, - "npm_config_cache": managedCachePath, - }, - }, function(err, stdout, stderr) { - debug([err, stdout, stderr]); - - if (err) return callback(err, stdout, err.message); - - stdout = stdout.trimRight(); - stderr = stderr.trimRight(); - - callback(null, stdout, stderr); - }); - } - - /* -- Package Management ----- */ - - function npmOutdated(pkgs, callback) { - npmExec("outdated", ["--json"], function(err, stdout, stderr) { - if (err) - return callback(err, null, stdout, stderr); - - if (!stdout) - return callback(null, null, stdout, stderr); - - var outdated = JSON.parse(stdout); - outdated = Object.keys(outdated); - - callback(null, outdated, stdout, stderr); - }); - } - - function npmInstall(pkgs, callback) { - npmExec("install", ["--"].concat(pkgs), function(err, stdout, stderr) { - callback(err, stdout, stderr); - }); - } - - function npmUpdate(pkgs, callback) { - npmExec("update", ["--"].concat(pkgs), function(err, stdout, stderr) { - callback(err, stdout, stderr); - }); - } - - function npmExplorePath(pkg, callback) { - npmExec("explore", [pkg, "--", "pwd"], function(err, stdout, stderr) { - if (err) - return callback(err, null, stderr); - - callback(null, stdout, stderr); - }); - } - - /** - * Build the symbolic links from `~/.c9/plugins` to the managed plugins. - * - * @param {String[]} pkgs A list of package names to link. - * - * @param {Function} callback - * @param {Error=} callback.err - */ - function buildLinks(pkgs, callback) { - async.each(pkgs, function(pkg, done) { - npmExplorePath(pkg, function(err, pkgPath) { - if (err) return done(err); - fsLink(pkgPath, done); - }); - }, callback); - } - - /** - * Removes symbolic links from the `~/.c9/managed/plugins` folder. - */ - function fsRmLinks(callback) { - debug("find", { args: [ managedPluginsPath, "-maxdepth", "1", "-type", "l", "-exec", "rm", "{}", ";" ]}); - - // find . -maxdepth 1 -type l -exec rm {} \; - - proc.execFile("find", { - args: [ - managedPluginsPath, - "-maxdepth", "1", - "-type", "l", - "-exec", "rm", "{}", ";" - ], - }, function(err, stdout, stderr) { - debug([err, stdout, stderr]); - callback(err, stdout, stderr); - }); - } - - /** - * Create a symbolic link in `~/.c9/plugins` pointing to the given - * plugin path. - * - * @param {String} pkgPath Path to the source package folder - */ - function fsLink(pkgPath, callback) { - debug("ln", { args: [ "-s", "-f", pkgPath, [ managedPluginsPath, "." ].join("/") ]}); - - proc.execFile("ln", { - args: [ - "-s", "-f", - pkgPath, - managedPluginsPath + "/.", - ], - }, function(err, stdout, stderr) { - debug([err, stdout, stderr]); - callback(err, stdout, stderr); - }); - } - - function fsMkdirs(dirPaths, callback) { - debug("mkdir", { args: [ "-p", "--" ].concat(dirPaths) }); - - proc.execFile("mkdir", { - args: [ "-p", "--" ].concat(dirPaths), - }, function(err, stdout, stderr) { - callback(err, stdout, stderr); - }); - } - - function fsWriteNpmrc(callback) { - var config = [ - "//registry.npmjs.org/:_authToken = a7c61f6e-5b10-41db-947f-8bc8f1f9468b", - "production = true", - "depth = 0", - "userconfig = /dev/null", - "prefix = " + managedNpmPath, - "cache = " + managedCachePath, - ]; - - // - // HACK: - fs.writeFile() does not always work? we are using echo - // instead - // - // - config is not escaped - // - - debug("sh", { args: [ "-c", "echo '" + config.join("\\n") + "' > " + managedRcPath ]}); - - proc.execFile("sh", { - args: [ - "-c", - "echo '" + config.join("\\n") + "' > " + managedRcPath - ], - }, function(err, stdout, stderr) { - callback(err, stdout, stderr); - }); - } - - /* -- Interface ----- */ - - /** - * Show a modal upgrade progress dialog, blocking the IDE interface - * while we are updating the plugins. - */ - function showUpdateDialog() { - var dialog = new Dialog("Ajax.org", [], { - name: "plugin.updater.npm.dialog", - class: "dialog-updater", - allowClose: false, - modal: true, - elements: [ - ], - }); - - dialog.title = "Installing Updates"; - dialog.heading = ""; - dialog.body = "Your workspace will be updated to the newest version and reload automatically."; - - dialog.show(); - - return dialog; - } - - /** - * Show an upgrade error dialog, requesting to delete and recreate the - * workspace. This is shown for critical update errors. - */ - function showErrorDialog(err) { - var dialog = new Dialog("Ajax.org", [], { - name: "plugin.updater.npm.error_dialog", - allowClose: true, - modal: true, - elements: [ - ], - }); - - var errorMessage = (err && err.message) ? "" + err.message : err; - - dialog.title = "Error installing updates"; - dialog.heading = ""; - dialog.body = "Important updates could not be installed on this workspace.

" - + "Please delete this workspace and create a new one, in order to continue " - + "working in an up-to-date environment.

" - + "
" + errorMessage + "
"; - - dialog.show(); - - return dialog; - } - - /***** Register and define API *****/ - - plugin.on("load", load); - plugin.on("unload", unload); - - /** - * @class salesforc.sync - */ - plugin.freezePublicAPI({ - }); - - register(null, { - "plugin.updater.npm": plugin - }); - } -}); - diff --git a/plugins/c9.ide.save/save.js b/plugins/c9.ide.save/save.js index c8eed027..c52361af 100644 --- a/plugins/c9.ide.save/save.js +++ b/plugins/c9.ide.save/save.js @@ -98,7 +98,6 @@ define(function(require, exports, module) { name: "reverttosavedall", hint: "downgrade the all open tabs to the last saved version", bindKey: { mac: "Option-Shift-Q", win: "Alt-Shift-Q" }, - isAvailable: available, exec: function () { revertToSavedAll(); } diff --git a/plugins/c9.ide.ui/lib/menu/menu.js b/plugins/c9.ide.ui/lib/menu/menu.js index d5e2e405..1118e70e 100644 --- a/plugins/c9.ide.ui/lib/menu/menu.js +++ b/plugins/c9.ide.ui/lib/menu/menu.js @@ -1520,7 +1520,7 @@ apf.item = function(struct, tagName) { return; var opener = this.parentNode.opener; - if (opener && opener.parentNode.$showingSubMenu) + if (opener && opener.parentNode && opener.parentNode.$showingSubMenu) selectItem(opener); if (!force && (apf.isChildOf(this.$ext, e.toElement || e.explicitOriginalTarget) diff --git a/plugins/c9.ide.ui/widgets.less b/plugins/c9.ide.ui/widgets.less index adc65842..45dc6776 100644 --- a/plugins/c9.ide.ui/widgets.less +++ b/plugins/c9.ide.ui/widgets.less @@ -65,6 +65,22 @@ background-color: #565656; background-position: -2px -18px; } +.ace_tree .tree-row .checkbox.loading:after{ + content:" "; + position:relative; + display:block; + top:0; left:0; + width: 100%; + height: 100%; + border-radius: 20%; + background-color: @color; + animation: scaleout 1.2s infinite ease-in-out +} +@keyframes scaleout { + 0% { transform: scale(0) } + 80% { transform: scale(1); opacity: 0;} + 100% { transform: scale(1); opacity: 0;} +} .custom-tree.ace_tree .tree-row .toggler{ .image-2x("@{tree-arrow}", 20px, 10px); diff --git a/plugins/c9.vfs.standalone/views/standalone.html.ejs b/plugins/c9.vfs.standalone/views/standalone.html.ejs index 1468c436..d51634aa 100644 --- a/plugins/c9.vfs.standalone/views/standalone.html.ejs +++ b/plugins/c9.vfs.standalone/views/standalone.html.ejs @@ -14,7 +14,7 @@ return obj; } window.isLocalVersion = nRequire ? true : false; - window.require = undefined; + window.require = window.module = undefined; @@ -64,8 +64,6 @@ } }); - app.lut = {}; - app.on("error", function(err){ console.error(err.stack); if (!errored) @@ -73,20 +71,11 @@ }); app.on("service", function(name, plugin, options){ - if (name == "plugin.loader" || name == "plugin.installer" - || name == "plugin.debug" || name == "plugin.manager" - || name == "plugin.test") - plugin.architect = app; if (!plugin.name) plugin.name = name; - if (options) - app.lut[(options.packagePath || "").replace(/^.*\/home\/.c9\//, "")] = options; }); - app.on("ready", function(){ - if (app.services.configure) - app.services.configure.services = app.services; - + app.once("ready", function() { window.app = app.services; window.app.__defineGetter__("_ace", function(){ return this.tabManager.focussedTab.editor.ace; diff --git a/plugins/c9.vfs.standalone/www/ide.html b/plugins/c9.vfs.standalone/www/ide.html index c5479419..e20a98f1 100644 --- a/plugins/c9.vfs.standalone/www/ide.html +++ b/plugins/c9.vfs.standalone/www/ide.html @@ -124,29 +124,18 @@ } }); - app.lut = {}; - app.on("error", function(err){ console.error(err.stack); if (!errored) alert(err); }); - app.on("service", function(name, plugin, options){ - if (name == "plugin.loader" || name == "plugin.installer" - || name == "plugin.debug" || name == "plugin.manager" - || name == "plugin.test") - plugin.architect = app; + app.on("service", function(name, plugin, options) { if (!plugin.name) plugin.name = name; - if (options) - app.lut[(options.packagePath || "").replace(/^.*\/home\/.c9\//, "")] = options; }); app.on("ready", function() { - if (app.services.configure) - app.services.configure.services = app.services; - window.app = app.services; window.app.__defineGetter__("_ace", function(){ return this.tabManager.focussedTab.editor.ace;