diff --git a/plugins/c9.ide.preferences/preferences.js b/plugins/c9.ide.preferences/preferences.js index 87f12597..d9251db8 100644 --- a/plugins/c9.ide.preferences/preferences.js +++ b/plugins/c9.ide.preferences/preferences.js @@ -192,7 +192,7 @@ define(function(require, exports, module) { var panels = navigation && navigation.$ext && navigation.$ext.children; if (panels) { for (var i = 0; i < panels.length; i++) { - if (panels[i].name == panel) { + if (panels[i].name == panel || panels[i].name == "preferences." + panel) { panel = panels[i].hostPlugin; break; } diff --git a/plugins/c9.ide.preview/preview.js b/plugins/c9.ide.preview/preview.js index 0f8ed1f1..d7c866f5 100644 --- a/plugins/c9.ide.preview/preview.js +++ b/plugins/c9.ide.preview/preview.js @@ -64,27 +64,8 @@ define(function(require, exports, module) { if (!options.hideButton) { var submenu = new ui.menu({ "onprop.visible": function(e) { - var tab = tabs.focussedTab; - var isKnown = false; - - if (tab && tab.path) { - var path = tab.path; - for (var name in previewers) { - if (previewers[name].matcher(path)) { - isKnown = true; - break; - } - } - - liveMenuItem.setAttribute("caption", isKnown - ? "Live Preview File (" + basename(path) + ")" - : "Raw Content of " + basename(path) - ); - liveMenuItem.enable(); - } - else { - liveMenuItem.disable(); - } + if (e.value) + updatePreviewMenu(e, submenu); } }); @@ -99,7 +80,7 @@ define(function(require, exports, module) { menus.addItemByPath("Tools/Preview/", submenu, 1000, handle); liveMenuItem = new ui.item({ - onclick: function(e) { commands.exec("preview", { newTab: e && e.button == 1 }); } + onclick: function(e) { commands.exec("preview", null, { newTab: e && e.button == 1 }); } }); menus.addItemByPath("Tools/Preview/Live Preview Files", liveMenuItem, 100, handle); @@ -111,6 +92,14 @@ define(function(require, exports, module) { }); } }), 200, handle); + menus.addItemByPath("Tools/Preview/~", new ui.divider({}), 2000, handle); + menus.addItemByPath("Tools/Preview/~", new ui.divider({}), 4000, handle); + menus.addItemByPath("Tools/Preview/Configure Preview URL...", new ui.item({ + onclick: function(e) { commands.exec("openpreferences", null, {panel: "project", section: "Run & Debug"}); } + }), 4200, handle); + menus.addItemByPath("Tools/Preview/Show Active Servers...", new ui.item({ + onclick: function(e) { commands.exec("showprocesslist", null, { mode: "lsof" }); } + }), 4300, handle); } settings.on("read", function(e) { @@ -122,18 +111,18 @@ define(function(require, exports, module) { }, handle); // Context menu for tree - var itemCtxTreePreview = new ui.item({ - match: "file", - caption: "Preview", - isAvailable: function() { - return tree.selectedNode && !tree.selectedNode.isFolder - && (options.local || util.normalizePath(tree.selectedNode.path).charAt(0) != "~"); - }, - onclick: function() { - openPreview(tree.selected); - } - }); tree.getElement("mnuCtxTree", function(mnuCtxTree) { + var itemCtxTreePreview = new ui.item({ + match: "file", + caption: "Preview", + isAvailable: function() { + return tree.selectedNode && !tree.selectedNode.isFolder + && (options.local || util.normalizePath(tree.selectedNode.path).charAt(0) != "~"); + }, + onclick: function() { + openPreview(tree.selected); + } + }); ui.insertByIndex(mnuCtxTree, itemCtxTreePreview, 160, handle); }); @@ -163,70 +152,30 @@ define(function(require, exports, module) { var nodes = tab.pane.group; if (!nodes) pane = tab.pane.hsplit(true); - else { + else pane = nodes[nodes.indexOf(tab.pane) === 0 ? 1 : 0]; - } } return pane; } if (args.server) { - var hostname = c9.hostname || "localhost:8080"; + var openInNewTab = args.newTab; + path = args.url; - var cb = function(err, stderr, stdout) { - if (err && err.code != 1) - showError("Could not check if server is running."); - else if (stderr || !stdout || !stdout.length) { - - // Check for project run config - var json = settings.getJson("project/run/configs") || {}; - for (var name in json) { - if (json[name]["default"]) { - commands.exec("run", null, { - callback: function(proc) { - proc.on("started", function() { - setTimeout(done, 1000); - }); - } - }); - return; - } - } - - warnNoServer(hostname); - } - - done(); - }; + if (!path) + path = "https://$C9_HOSTNAME"; - function done() { - var path = (options.local ? "http" : "https") - + "://" + hostname; - if (args.newTab) - return util.openNewWindow(path); - - // Open Pane - pane = findPane(); - - // Open Preview - openPreview(path, pane, args && args.active); - } + path = expandUrl(path); - if (args.nocheck) - done(); - else if (options.local) { - proc.execFile("lsof", { - args: ["-i", ":8080"] - }, cb); - } - else { - proc.execFile("nc", { - args: ["-zv", hostname, "80"] - }, cb); - } + if (window.location.protocol == "https:" && !path.startsWith("https:")) + openInNewTab = true; - return; + if (openInNewTab) + return util.openNewWindow(path); + + pane = findPane(); + return openPreview(path, pane, args && args.active); } else if (args.path) { path = args.path; @@ -284,6 +233,20 @@ define(function(require, exports, module) { var key = commands.getHotkey("reloadpreview"); if (commands.platform == "mac") key = apf.hotkeys.toMacNotation(key); + + prefs.add({ + "Project": { + position: 100, + "Run & Debug": { + position: 300, + "Preview URL": { + type: "textbox", + path: "project/preview/@url" + }, + } + } + }, handle); + prefs.add({ "Run": { position: 600, @@ -312,7 +275,7 @@ define(function(require, exports, module) { { caption: "Only on " + key, value: "false" }, { caption: "Always", value: "true" }, ] - } + }, } } }, handle); @@ -357,6 +320,13 @@ define(function(require, exports, module) { return pane; } + function expandUrl(url) { + var hostname = c9.hostname; + if (!c9.hosted && !hostname) + hostname = window.location.hostname; + return url.replace(/\$C9_HOSTNAME\b/, hostname); + } + function registerPlugin(plugin, matcher) { previewers[plugin.name] = { plugin: plugin, @@ -409,6 +379,73 @@ define(function(require, exports, module) { + "and change the hostname in the location bar."); } + function updatePreviewMenu(e, submenu) { + var tab = tabs.focussedTab; + var isKnown = false; + var title = "Live Preview File"; + if (tab && tab.path) { + var path = tab.path; + for (var name in previewers) { + if (previewers[name].matcher(path)) { + isKnown = true; + break; + } + } + if (!isKnown) { + title = "Raw Content of " + basename(path); + isKnown = true; + } + else { + title += " (" + basename(path) + ")"; + } + } + + liveMenuItem.setAttribute("caption", title); + if (isKnown) + liveMenuItem.enable(); + else + liveMenuItem.disable(); + // user configured elements + var url = settings.get("project/preview/@url"); + if (!Array.isArray(url)) url = [url]; + var added = false; + var index = 0; + var children = submenu.childNodes; + while (children[index] && children[index].localName != "divider") { + index++; + } + var appMenu = children[index - 1]; + var firstDivider = children[index]; + index++; + for (var i = 0; i < url.length; i++) { + if (typeof url[i] != "string" || !url[i]) continue; + var oldNode = children[index]; + if (!oldNode || oldNode.localName != "item") { + oldNode = submenu.insertBefore(new ui.item({ + onclick: openUrl + }), oldNode); + } + oldNode.value = url[i]; + var caption = "Open " + expandUrl(url[i]); + oldNode.setAttribute("caption", caption); + added = true; + index++; + } + while(index < children.length - 3) + submenu.removeChild(children[index]); + + appMenu.setAttribute("visible", !added); + firstDivider.setAttribute("visible", added); + + function openUrl(e) { + commands.exec("preview", null, { + newTab: e.button == 1, + url: e.currentTarget.value, + server: true + }); + } + } + /** * The preview handle, responsible for managing preview plugins. * This is the object you get when you request the preview diff --git a/plugins/c9.ide.processlist/processlist.js b/plugins/c9.ide.processlist/processlist.js index eca5f425..5304805d 100644 --- a/plugins/c9.ide.processlist/processlist.js +++ b/plugins/c9.ide.processlist/processlist.js @@ -1,6 +1,6 @@ define(function(require, exports, module) { main.consumes = [ - "ui", "layout", "commands", "Dialog", "proc", "util", "menus" + "ui", "layout", "commands", "Dialog", "proc", "util", "menus", "dialog.error" ]; main.provides = ["processlist"]; return main; @@ -15,6 +15,7 @@ define(function(require, exports, module) { var proc = imports.proc; var util = imports.util; var menus = imports.menus; + var showError = imports["dialog.error"].show; var search = require("../c9.ide.navigate/search"); var Tree = require("ace_tree/tree"); @@ -47,6 +48,8 @@ define(function(require, exports, module) { var INTERVAL = 5000; var model, datagrid, btnKill, btnForceKill, tbFilter, timer; + var mode = "ps"; + var loaded = false; function load() { if (loaded) return false; @@ -55,8 +58,8 @@ define(function(require, exports, module) { commands.addCommand({ name: "showprocesslist", bindKey: { mac: "Command-Option-P", win: "Ctrl-Alt-P" }, - exec: function() { - plugin.show(); + exec: function(editor, args) { + plugin.show(args.mode || "ps"); } }, plugin); @@ -82,10 +85,11 @@ define(function(require, exports, module) { model.$sortNodes = true; model.$sorted = true; - model.columns = [{ + model.columnsPs = [{ caption: "Process Name", value: "name", - width: "100%" + width: "100%", + type: "tree", }, { caption: "CPU", // value: "cpu", @@ -99,7 +103,7 @@ define(function(require, exports, module) { }, { caption: "Process Time", value: "ptime", - width: "100", + width: "50", }, { caption: "PID", value: "pid", @@ -110,6 +114,30 @@ define(function(require, exports, module) { width: "80", }]; + model.columnsLsof = [{ + caption: "Address", + value: "address", + width: "100%", + }, { + caption: "Process Name", + value: "command", + width: "80", + }, { + caption: "Status", + value: "status", + width: "50", + }, { + caption: "PID", + value: "pid", + width: "50", + }, { + caption: "Type", + value: "type", + width: "50", + }]; + + model.columns = model.columnsPs; + var datagridDiv = pNode.appendChild(document.createElement("div")); datagrid = new Tree(datagridDiv); datagrid.renderer.setTheme({ cssClass: "blackdg" }); @@ -148,50 +176,112 @@ define(function(require, exports, module) { applyFilter(); }); - updateProcessList(); + update(); emit("draw"); } /***** Methods *****/ + function update() { + if (mode == "ps") + updateProcessList(); + else + updateServerList(); + } + function updateProcessList() { - var sel = datagrid.selection.getSelectedNodes(); - - proc.execFile("ps", { args: ["auxc"]}, function(err, stdout, stderr) { + setModel(null, model.columnsPs); + proc.execFile("ps", { args: ["axh", "-ouser,pid:1,ppid:1,pcpu:1,pmem:1,time:1,command:1"] }, function(err, stdout, stderr) { if (err) return; + var hiddenRegex = /^\[kthreadd|/ - var lines = stdout.substr(0, stdout.length - 1).split("\n"); lines.shift(); + var oldNodes = model.pidMap; + if (!oldNodes || oldNodes.mode != mode) + oldNodes = { mode: mode }; + var pidMap = model.pidMap = { mode: mode }; + + var lines = stdout.substr(0, stdout.length - 1).split("\n"); var json = lines.map(function(line) { var item = line.split(/\s+/); - var name = item.splice(10).join(" "); - return { - name: name, - cpu: item[2], - mem: item[3], - ptime: item[9], - pid: item[1], - user: item[0] - }; + var name = item.slice(6).join(" "); + var pid = item[1]; + var node = oldNodes[pid] || { pid: pid, isOpen: hiddenRegex.test(name) }; + pidMap[pid] = node; + + node.cpu = item[3]; + node.mem = item[4]; + node.ppid = item[2]; + node.name = name; + node.user = item[0]; + node.ptime = item[5]; + + node.items = node.children = null; + + return node; }); + var root = []; + json.forEach(function(node) { + var parent = pidMap[node.ppid]; + if (!parent) return root.push(node); + if (!parent.items) parent.items = []; + parent.items.push(node); + }); + json = root; + + setModel(json, model.columnsPs); + }); + } + + function updateServerList() { + setModel(null, model.columnsLsof); + proc.execFile("bash", { args: ["-c", + "sudo -n lsof -P -i -F pcnTtu || lsof -P -i -F pcnTtu" + ]}, function(err, stdout, stderr) { + if (err) return; + var json = []; + var node; + var oldNodes = model.pidMap; + if (!oldNodes || oldNodes.mode != mode) + oldNodes = { mode: mode }; + model.pidMap = { mode: mode }; + stdout.split("\n").forEach(function(part) { + if (part[0] == "p") { + if (node) json.push(node); + var pid = part.slice(1); + node = oldNodes[pid] || { pid: pid }; + model.pidMap[pid] = node; + } + if (part[0] == "c") + node.command = part.slice(1); + if (part[0] == "n") + node.address = part.slice(1); + if (part[0] == "T" && /^TST=/.test(part)) + node.status = part.slice(4); + if (part[0] == "t") + node.type = part.slice(1); + if (part[0] == "u") + node.uid = parseInt(part.slice(1), 10); + }); + if (node) json.push(node); + + setModel(json, model.columnsLsof); + }); + } + + function setModel(json, columns) { + if (model.columns != columns) { + model.columns = columns; + datagrid.setDataProvider(model); + if (!json) json = []; + } + if (json) { model.cachedRoot = json; model.setRoot(json); - - if (model.keyword) - applyFilter(); - - if (sel) { - var nodes = []; - var pids = sel.map(function(n) { return n.pid; }); - - model.root.items.forEach(function(item) { - if (pids.indexOf(item.pid) > -1) - nodes.push(item); - }); - datagrid.selection.setSelection(nodes); - } - }); + } + if (model.keyword) + applyFilter(); } function forceKill() { @@ -201,22 +291,21 @@ define(function(require, exports, module) { function kill(force) { var nodes = datagrid.selection.getSelectedNodes(); if (!nodes.length) return; - var button = force ? btnForceKill : btnKill; async.each(nodes, function(row, next) { + if (!/^\d+$/.test(row.pid)) return next(); button.disable(); - - var args = []; - if (force) args.push("-9"); - args.push(row.pid); - - proc.execFile("kill", { args: args }, function(err, stdout, stderr) { + var args = (force ? "" : "-9 ") + row.pid; + proc.execFile("bash", { + args: ["-c", "sudo -n kill " + args + " || kill " + args] + }, function(err, stdout, stderr) { next(err); }); }, function(err) { button.enable(); - if (!err) updateProcessList(); + if (err) return showError(err); + update(); }); } @@ -235,29 +324,33 @@ define(function(require, exports, module) { } } - function show(reset, options) { + function show(mode, options) { if (!options) options = {}; + setMode(mode); return plugin.queue(function() { - // if (reset || current == -1) { - // path = [startPage]; - // current = 0; - // activate(startPage); - // } }, true); } + function setMode(val) { + if ((val == "ps" || val == "lsof") && mode != val) { + mode = val; + if (timer) update(); + } + } + /***** Lifecycle *****/ plugin.on("show", function() { timer = setInterval(function() { - updateProcessList(); + update(); }, INTERVAL); + update(); }); plugin.on("hide", function() { - clearInterval(timer); + timer = clearInterval(timer); }); plugin.on("draw", function(options) { draw(options);