kopia lustrzana https://github.com/c9/core
1935 wiersze
69 KiB
JavaScript
1935 wiersze
69 KiB
JavaScript
/*global apf*/
|
|
define(function(require, exports, module) {
|
|
main.consumes = [
|
|
"Plugin", "settings", "menus", "preferences", "commands",
|
|
"tabManager", "ui", "save", "panels", "tree", "Menu", "fs",
|
|
"dialog.question", "clipboard"
|
|
];
|
|
main.provides = ["tabbehavior"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var Plugin = imports.Plugin;
|
|
var settings = imports.settings;
|
|
var tabs = imports.tabManager;
|
|
var menus = imports.menus;
|
|
var Menu = imports.Menu;
|
|
var commands = imports.commands;
|
|
var clipboard = imports.clipboard;
|
|
var tree = imports.tree;
|
|
var save = imports.save;
|
|
var panels = imports.panels;
|
|
var ui = imports.ui;
|
|
var fs = imports.fs;
|
|
var prefs = imports.preferences;
|
|
var question = imports["dialog.question"].show;
|
|
|
|
/***** Initialization *****/
|
|
|
|
var plugin = new Plugin("Ajax.org", main.consumes);
|
|
// var emit = plugin.getEmitter();
|
|
|
|
var mnuContext, mnuEditors, mnuTabs;
|
|
var menuItems = [], menuClosedItems = [];
|
|
|
|
var paneList = [];
|
|
var accessedPane = 0;
|
|
|
|
var cycleKeyPressed, changedTabs, unchangedTabs, dirtyNextTab, dirtyNextPane;
|
|
|
|
var ACTIVEPAGE = function() { return tabs.focussedTab; };
|
|
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; };
|
|
|
|
var movekey = "Command-Option-Shift";
|
|
var definition = [
|
|
["clonetab", "", "", ACTIVEPAGE, "create a new tab with a view on the same file"],
|
|
["closetab", "Option-W", "Alt-W", ACTIVEPAGE, "close the tab that is currently active"],
|
|
["closealltabs", "Option-Shift-W", "Alt-Shift-W", ACTIVEPAGE, "close all opened tabs"],
|
|
["closeallbutme", "Option-Ctrl-W", "Ctrl-Alt-W", MORETABS, "close all opened tabs, except the tab that is currently active"],
|
|
["gototabright", "Command-]", "Ctrl-]", MORETABSINPANE, "navigate to the next tab, right to the tab that is currently active"],
|
|
["gototableft", "Command-[", "Ctrl-[", MORETABSINPANE, "navigate to the next tab, left to the tab that is currently active"],
|
|
["movetabright", movekey + "-Right", "Ctrl-Meta-Right", MORETABS, "move the tab that is currently active to the right. Will create a split tab to the right if it's the right most tab."],
|
|
["movetableft", movekey + "-Left", "Ctrl-Meta-Left", MORETABS, "move the tab that is currently active to the left. Will create a split tab to the left if it's the left most tab."],
|
|
["movetabup", movekey + "-Up", "Ctrl-Meta-Up", MORETABS, "move the tab that is currently active to the up. Will create a split tab to the top if it's the top most tab."],
|
|
["movetabdown", movekey + "-Down", "Ctrl-Meta-Down", MORETABS, "move the tab that is currently active to the down. Will create a split tab to the bottom if it's the bottom most tab."],
|
|
["tab1", "Command-1", "Ctrl-1", null, "navigate to the first tab"],
|
|
["tab2", "Command-2", "Ctrl-2", null, "navigate to the second tab"],
|
|
["tab3", "Command-3", "Ctrl-3", null, "navigate to the third tab"],
|
|
["tab4", "Command-4", "Ctrl-4", null, "navigate to the fourth tab"],
|
|
["tab5", "Command-5", "Ctrl-5", null, "navigate to the fifth tab"],
|
|
["tab6", "Command-6", "Ctrl-6", null, "navigate to the sixth tab"],
|
|
["tab7", "Command-7", "Ctrl-7", null, "navigate to the seventh tab"],
|
|
["tab8", "Command-8", "Ctrl-8", null, "navigate to the eighth tab"],
|
|
["tab9", "Command-9", "Ctrl-9", null, "navigate to the ninth tab"],
|
|
["tab0", "Command-0", "Ctrl-0", null, "navigate to the tenth tab"],
|
|
["revealtab", "Command-Shift-L", "Ctrl-Shift-L", ACTIVEPATH, "reveal current tab in the file tree"],
|
|
["nexttab", "Option-Tab", "Ctrl-Tab|Alt-`", MORETABSINPANE, "navigate to the next tab in the stack of accessed tabs"],
|
|
["previoustab", "Option-Shift-Tab", "Ctrl-Shift-Tab|Alt-Shift-`", MORETABSINPANE, "navigate to the previous tab in the stack of accessed tabs"],
|
|
["nextpane", "Option-ESC", "Ctrl-`", MOREPANES, "navigate to the next tab in the stack of panes"],
|
|
["previouspane", "Option-Shift-ESC", "Ctrl-Shift-`", MOREPANES, "navigate to the previous tab in the stack of panes"],
|
|
["gotopaneright", "Ctrl-Meta-Right", "Ctrl-Meta-Right", null, "navigate to the pane on the right"],
|
|
["gotopaneleft", "Ctrl-Meta-Left", "Ctrl-Meta-Left", null, "navigate to the pane on the left"],
|
|
["gotopaneup", "Ctrl-Meta-Up", "Ctrl-Meta-Up", null, "navigate to the pane on the top"],
|
|
["gotopanedown", "Ctrl-Meta-Down", "Ctrl-Meta-Down", null, "navigate to the pane on the bottom"],
|
|
["reopenLastTab", "Option-Shift-T", "Alt-Shift-T", function() {
|
|
return menuClosedItems.length;
|
|
}, "reopen last closed tab"],
|
|
["closealltotheright", "", "", function() {
|
|
var tab = mnuContext.$tab || mnuContext.$pane && mnuContext.$pane.getTab();
|
|
if (tab) {
|
|
var pages = tab.pane.getTabs();
|
|
return pages.pop() != tab;
|
|
}
|
|
}, "close all tabs to the right of the focussed tab"],
|
|
["closealltotheleft", "", "", function() {
|
|
var tab = mnuContext.$tab || mnuContext.$pane && mnuContext.$pane.getTab();
|
|
if (tab) {
|
|
var pages = tab.pane.getTabs();
|
|
return pages.length > 1 && pages[0] != tab;
|
|
}
|
|
}, "close all tabs to the left of the focussed tab"],
|
|
["closepane", "Command-Ctrl-W", "Ctrl-W", function() {
|
|
return mnuContext.$tab || mnuContext.$pane || tabs.getTabs().length;
|
|
}, "close this pane"],
|
|
["nosplit", "", "", null, "no split"],
|
|
["hsplit", "", "", null, "split the current pane in two columns and move the active tab to it"],
|
|
["vsplit", "", "", null, "split the current pane in two rows and move the active tab to it"],
|
|
["twovsplit", "", "", null, "create a two pane row layout"],
|
|
["twohsplit", "", "", null, "create a two pane column layout"],
|
|
["foursplit", "", "", null, "create a four pane layout"],
|
|
["threeleft", "", "", null, "create a three pane layout with the stack on the left side"],
|
|
["threeright", "", "", null, "create a three pane layout with the stack on the right side"]
|
|
];
|
|
|
|
var loaded = false;
|
|
function load() {
|
|
if (loaded) return false;
|
|
loaded = true;
|
|
|
|
// Settings
|
|
settings.on("read", function(e) {
|
|
settings.setDefaults("user/general", [["revealfile", false]]);
|
|
|
|
var list = settings.getJson("state/panecycle");
|
|
if (list) {
|
|
list.remove(null);
|
|
paneList = list;
|
|
}
|
|
}, plugin);
|
|
|
|
settings.on("write", function(e) {
|
|
var list;
|
|
|
|
if (paneList.changed) {
|
|
list = [];
|
|
paneList.forEach(function(tab, i) {
|
|
if (tab && tab.name)
|
|
list.push(tab.name);
|
|
});
|
|
settings.setJson("state/panecycle", list);
|
|
paneList.changed = false;
|
|
}
|
|
}, plugin);
|
|
|
|
// Preferences
|
|
prefs.add({
|
|
"General": {
|
|
"Tree & Navigate": {
|
|
"Reveal Active File in Project Tree": {
|
|
type: "checkbox",
|
|
position: 4000,
|
|
path: "user/general/@revealfile"
|
|
}
|
|
}
|
|
}
|
|
}, plugin);
|
|
|
|
// Commands
|
|
definition.forEach(function(item) {
|
|
commands.addCommand({
|
|
name: item[0],
|
|
bindKey: { mac: item[1], win: item[2] },
|
|
group: "Tabs",
|
|
hint: item[4],
|
|
isAvailable: item[3],
|
|
exec: function(editor, arg) {
|
|
if (arg && !arg[0] && arg.source == "click")
|
|
arg = [mnuContext.$tab, mnuContext.$pane];
|
|
plugin[item[0]].apply(plugin, arg);
|
|
}
|
|
}, plugin);
|
|
});
|
|
|
|
commands.addCommand({
|
|
name: "refocusTab",
|
|
bindKey: { mac: "Esc", win: "Esc", position: -1000 },
|
|
group: "Tabs",
|
|
isAvailable: function() {
|
|
var el = apf.activeElement;
|
|
if (el && (el.tagName == "page" || el.tagName == "menu"))
|
|
return false;
|
|
return !!tabs.focussedTab;
|
|
},
|
|
exec: function(e) {
|
|
if (tabs.focussedTab)
|
|
tabs.focusTab(tabs.focussedTab);
|
|
},
|
|
passEvent: true
|
|
}, plugin);
|
|
|
|
commands.addCommand({
|
|
name: "copyFilePath",
|
|
group: "",
|
|
isAvailable: function() {
|
|
var el = apf.popup.getCurrentElement();
|
|
if (el && el.visible) {
|
|
if (el.$tab)
|
|
return !!(el.$tab.path || el.$tab.relatedPath || el.$tab.editor.getPathAsync);
|
|
}
|
|
return true;
|
|
},
|
|
exec: function(editor, args) {
|
|
var text = "";
|
|
var el = apf.popup.getCurrentElement();
|
|
var fromContextMenu = args && args.source == "click";
|
|
var tab;
|
|
if (!fromContextMenu || !el) {
|
|
tab = tabs.focussedTab;
|
|
text = tab.path || tab.relatedPath;
|
|
}
|
|
else if (el.name == "mnuCtxTree") {
|
|
text = tree.selectedNodes.map(function(n) {
|
|
return n.path;
|
|
}).join("\n");
|
|
}
|
|
else if (el.$tab) {
|
|
tab = el.$tab;
|
|
text = tab.path || tab.relatedPath;
|
|
}
|
|
|
|
if (text) {
|
|
clipboard.clipboardData.setData("text/plain", text);
|
|
}
|
|
else if (tab && tab.editor.getPathAsync) {
|
|
tab.editor.getPathAsync(function(err, text) {
|
|
if (!err && text)
|
|
clipboard.clipboardData.setData("text/plain", text);
|
|
});
|
|
}
|
|
|
|
}
|
|
}, plugin);
|
|
|
|
// General Menus
|
|
menus.addItemByPath("File/~", new ui.divider(), 100000, plugin);
|
|
menus.addItemByPath("File/Close File", new ui.item({
|
|
command: "closetab"
|
|
}), 110000, plugin);
|
|
menus.addItemByPath("File/Close All Files", new ui.item({
|
|
command: "closealltabs"
|
|
}), 120000, plugin);
|
|
|
|
mnuTabs = new ui.menu();
|
|
|
|
menus.addItemByPath("Window/Tabs", mnuTabs, 10100, plugin);
|
|
menus.addItemByPath("Window/Tabs/Close Pane", new ui.item({
|
|
command: "closepane"
|
|
}), 100, plugin);
|
|
menus.addItemByPath("Window/Tabs/Close All Tabs In All Panes", new ui.item({
|
|
command: "closealltabs"
|
|
}), 200, plugin);
|
|
menus.addItemByPath("Window/Tabs/Close All But Current Tab", new ui.item({
|
|
command: "closeallbutme"
|
|
}), 300, plugin);
|
|
|
|
menus.addItemByPath("Window/Tabs/~", new ui.divider(), 1000000, plugin);
|
|
menus.addItemByPath("Window/Tabs/Split Pane in Two Rows", new ui.item({
|
|
command: "vsplit"
|
|
}), 1000100, plugin);
|
|
menus.addItemByPath("Window/Tabs/Split Pane in Two Columns", new ui.item({
|
|
command: "hsplit"
|
|
}), 1000200, plugin);
|
|
menus.addItemByPath("Window/Tabs/~", new ui.divider(), 1000300, plugin);
|
|
|
|
menus.addItemByPath("Window/Tabs/~", new apf.label({
|
|
class: "splits",
|
|
caption: [
|
|
["span", { class: "nosplit" }],
|
|
["span", { class: "twovsplit" }],
|
|
["span", { class: "twohsplit" }],
|
|
["span", { class: "foursplit" }],
|
|
["span", { class: "threeleft" }],
|
|
["span", { class: "threeright" }],
|
|
],
|
|
onclick: function(e) {
|
|
var span = e.htmlEvent.target;
|
|
if (!span || span.tagName != "SPAN") return;
|
|
plugin[span.className]();
|
|
mnuTabs.hide();
|
|
}
|
|
}), 1000400, plugin);
|
|
|
|
menus.addItemByPath("Window/~", new ui.divider(), 9000, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/", null, 9100, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/Tab to the Right", new ui.item({
|
|
command: "gototabright"
|
|
}), 100, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/Tab to the Left", new ui.item({
|
|
command: "gototableft"
|
|
}), 200, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/Next Tab in History", new ui.item({
|
|
command: "nexttab"
|
|
}), 300, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/Previous Tab in History", new ui.item({
|
|
command: "previoustab"
|
|
}), 400, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/~", new ui.divider(), 500, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/Move Tab to Right", new ui.item({
|
|
command: "movetabright"
|
|
}), 600, plugin);
|
|
menus.addItemByPath("Window/Navigation/Move Tab to Left", new ui.item({
|
|
command: "movetableft"
|
|
}), 700, plugin);
|
|
menus.addItemByPath("Window/Navigation/Move Tab to Up", new ui.item({
|
|
command: "movetabup"
|
|
}), 800, plugin);
|
|
menus.addItemByPath("Window/Navigation/Move Tab to Down", new ui.item({
|
|
command: "movetabdown"
|
|
}), 900, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/~", new ui.divider(), 1000, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/Go to Pane to Right", new ui.item({
|
|
command: "gotopaneright"
|
|
}), 1100, plugin);
|
|
menus.addItemByPath("Window/Navigation/Go to Pane to Left", new ui.item({
|
|
command: "gotopaneleft"
|
|
}), 1200, plugin);
|
|
menus.addItemByPath("Window/Navigation/Go to Pane to Up", new ui.item({
|
|
command: "gotopaneup"
|
|
}), 1300, plugin);
|
|
menus.addItemByPath("Window/Navigation/Go to Pane to Down", new ui.item({
|
|
command: "gotopanedown"
|
|
}), 1400, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/~", new ui.divider(), 1500, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/Next Pane in History", new ui.item({
|
|
command: "nextpane"
|
|
}), 1600, plugin);
|
|
|
|
menus.addItemByPath("Window/Navigation/Previous Pane in History", new ui.item({
|
|
command: "previouspane"
|
|
}), 1700, plugin);
|
|
|
|
// Tab Helper Menu
|
|
menus.addItemByPath("Window/~", new ui.divider(), 10000, plugin);
|
|
|
|
mnuTabs.addEventListener("prop.visible", function(e) {
|
|
if (e.value) {
|
|
if (mnuTabs.opener && mnuTabs.opener.parentNode.localName == "tab") {
|
|
mnuContext.$pane = mnuTabs.opener.parentNode.cloud9pane;
|
|
mnuContext.$tab = mnuContext.$pane.getTab();
|
|
}
|
|
updateTabMenu();
|
|
}
|
|
else {
|
|
removeContextInfo(e);
|
|
}
|
|
|
|
if (mnuTabs.opener && mnuTabs.opener["class"] == "tabmenubtn")
|
|
apf.setStyleClass(mnuTabs.$ext, "tabsContextMenu");
|
|
else
|
|
apf.setStyleClass(mnuTabs.$ext, "", ["tabsContextMenu"]);
|
|
}, true);
|
|
|
|
// Tree Context Menu
|
|
menus.addItemByPath("context/tree/Copy file path", new ui.item({
|
|
command: "copyFilePath"
|
|
}), 800, plugin);
|
|
menus.addItemByPath("context/tree/~", new ui.divider({}), 850, menus);
|
|
|
|
// Tab Context Menu
|
|
mnuContext = new Menu({ id: "mnuContext" }, plugin).aml;
|
|
menus.addItemByPath("context/tabs/", mnuContext, 0, plugin);
|
|
|
|
function removeContextInfo(e) {
|
|
if (!e.value) {
|
|
// use setTimeout because apf closes menu before menuitem onclick event
|
|
setTimeout(function() {
|
|
mnuContext.$tab = null;
|
|
mnuContext.$pane = null;
|
|
});
|
|
}
|
|
}
|
|
|
|
mnuContext.on("prop.visible", removeContextInfo, false);
|
|
|
|
menus.addItemByPath("Reveal in File Tree", new ui.item({
|
|
command: "revealtab"
|
|
}), 100, mnuContext, plugin);
|
|
menus.addItemByPath("~", new ui.divider(), 200, mnuContext, plugin);
|
|
menus.addItemByPath("Copy file path", new ui.item({
|
|
command: "copyFilePath"
|
|
}), 230, mnuContext, plugin);
|
|
menus.addItemByPath("~", new ui.divider(), 260, mnuContext, plugin);
|
|
menus.addItemByPath("Close Tab", new ui.item({
|
|
command: "closetab"
|
|
}), 300, mnuContext, plugin);
|
|
menus.addItemByPath("Close All Tabs", new ui.item({
|
|
command: "closepane"
|
|
}), 450, mnuContext, plugin);
|
|
menus.addItemByPath("Close Other Tabs", new ui.item({
|
|
command: "closeallbutme"
|
|
}), 500, mnuContext, plugin);
|
|
menus.addItemByPath("Close Tabs to the Left", new ui.item({
|
|
command: "closealltotheleft"
|
|
}), 600, mnuContext, plugin);
|
|
menus.addItemByPath("Close Tabs to the Right", new ui.item({
|
|
command: "closealltotheright"
|
|
}), 700, mnuContext, plugin);
|
|
menus.addItemByPath("~", new ui.divider(), 750, mnuContext, plugin);
|
|
menus.addItemByPath("Split Pane in Two Rows", new ui.item({
|
|
command: "vsplit"
|
|
}), 800, mnuContext, plugin);
|
|
menus.addItemByPath("Split Pane in Two Columns", new ui.item({
|
|
command: "hsplit"
|
|
}), 900, mnuContext, plugin);
|
|
menus.addItemByPath("~", new ui.divider(), 1000, mnuContext, plugin);
|
|
menus.addItemByPath("Duplicate View", new ui.item({
|
|
command: "clonetab"
|
|
}), 1010, mnuContext, plugin);
|
|
|
|
menus.addItemByPath("View/~", new ui.divider(), 800, plugin);
|
|
|
|
menus.addItemByPath("View/Layout/", null, 900, plugin);
|
|
|
|
menus.addItemByPath("View/Layout/Single", new ui.item({
|
|
command: "nosplit"
|
|
}), 100, mnuContext, plugin);
|
|
|
|
menus.addItemByPath("View/Layout/Vertical Split", new ui.item({
|
|
command: "twovsplit"
|
|
}), 100, mnuContext, plugin);
|
|
|
|
menus.addItemByPath("View/Layout/Horizontal Split", new ui.item({
|
|
command: "twohsplit"
|
|
}), 200, mnuContext, plugin);
|
|
|
|
menus.addItemByPath("View/Layout/Cross Split", new ui.item({
|
|
command: "foursplit"
|
|
}), 300, mnuContext, plugin);
|
|
|
|
menus.addItemByPath("View/Layout/Split 1:2", new ui.item({
|
|
command: "threeright"
|
|
}), 400, mnuContext, plugin);
|
|
|
|
menus.addItemByPath("View/Layout/Split 2:1", new ui.item({
|
|
command: "threeleft"
|
|
}), 500, mnuContext, plugin);
|
|
|
|
|
|
|
|
mnuEditors = tabs.getElement("mnuEditors");
|
|
var div, label;
|
|
div = menus.addItemToMenu(mnuEditors, new ui.divider(), 1000000, plugin);
|
|
label = menus.addItemToMenu(mnuEditors, new ui.item({
|
|
caption: "Recently Closed Tabs",
|
|
disabled: true
|
|
}), 1000001, plugin);
|
|
menuClosedItems.hide = function() { div.hide(); label.hide(); };
|
|
menuClosedItems.show = function() { div.show(); label.show(); };
|
|
menuClosedItems.hide();
|
|
|
|
// Other Hooks
|
|
tabs.on("paneCreate", function(e) {
|
|
var pane = e.pane.aml;
|
|
pane.on("contextmenu", function(e) {
|
|
if (e.currentTarget) {
|
|
mnuContext.$tab = e.currentTarget.tagName == "page"
|
|
? e.currentTarget.cloud9tab : null;
|
|
mnuContext.$pane = (mnuContext.$tab || 0).pane
|
|
|| e.currentTarget.cloud9pane;
|
|
}
|
|
if (ui.isChildOf(pane.$buttons, e.htmlEvent.target, true)) {
|
|
mnuContext.display(e.x, e.y);
|
|
return false;
|
|
}
|
|
});
|
|
|
|
var meta = e.pane.meta;
|
|
if (!meta.accessList)
|
|
meta.accessList = [];
|
|
if (!meta.accessList.toJson)
|
|
meta.accessList.toJson = accessListToJson;
|
|
}, plugin);
|
|
|
|
//@todo store the stack for availability after reload
|
|
tabs.on("tabBeforeClose", function(e) {
|
|
var tab = e.tab;
|
|
var event = e.htmlEvent || {};
|
|
|
|
// Shift = close all
|
|
if (event.shiftKey) {
|
|
closealltabs();
|
|
return false;
|
|
}
|
|
// Alt/ Option = close all but this
|
|
else if (event.altKey) {
|
|
closeallbutme(tab);
|
|
return false;
|
|
}
|
|
}, plugin);
|
|
|
|
tabs.on("tabAfterClose", function(e) {
|
|
// Hack to force focus on the right pane
|
|
var accessList = e.tab.pane.meta.accessList;
|
|
if (tabs.focussedTab == e.tab && accessList[1])
|
|
e.tab.pane.aml.nextTabInLine = accessList[1].aml;
|
|
}, plugin);
|
|
|
|
tabs.on("tabBeforeReparent", function(e) {
|
|
// Move to new access list
|
|
var lastList = e.lastPane.meta.accessList;
|
|
var accessList = e.tab.pane.meta.accessList;
|
|
lastList.splice(lastList.indexOf(e.tab), 1);
|
|
|
|
if (e.tab == tabs.focussedTab)
|
|
accessList.unshift(e.tab);
|
|
else
|
|
accessList.push(e.tab);
|
|
|
|
// Hack to force focus on the right pane
|
|
if (tabs.focussedTab == e.tab && lastList[0])
|
|
e.lastPane.aml.nextTabInLine = lastList[0].aml;
|
|
}, plugin);
|
|
|
|
tabs.on("tabAfterClose", function(e) {
|
|
var tab = e.tab;
|
|
if (tab.document.meta.preview)
|
|
return;
|
|
|
|
addTabToClosedMenu(tab);
|
|
tab.pane.meta.accessList.remove(tab);
|
|
paneList.remove(tab);
|
|
}, plugin);
|
|
|
|
tabs.on("tabCreate", function(e) {
|
|
var tab = e.tab;
|
|
|
|
if (tab.title) {
|
|
// @todo candidate for optimization using a hash
|
|
var path = tab.path || tab.editorType;
|
|
for (var i = menuClosedItems.length - 1; i >= 0; i--) {
|
|
if (menuClosedItems[i].path == path) {
|
|
menuClosedItems.splice(i, 1)[0].destroy(true, true);
|
|
if (!menuClosedItems.length)
|
|
menuClosedItems.hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tab.document.meta.preview)
|
|
return;
|
|
|
|
var accessList = tab.pane.meta.accessList;
|
|
var idx;
|
|
|
|
if (accessList.indexOf(tab) == -1) {
|
|
idx = accessList.indexOf(tab.name);
|
|
if (idx == -1) { //Load accesslist from index
|
|
if (tab == tabs.focussedTab)
|
|
accessList.unshift(tab);
|
|
else
|
|
accessList.push(tab); //splice(1, 0, tab);
|
|
}
|
|
else
|
|
accessList[idx] = tab;
|
|
}
|
|
if (paneList.indexOf(tab) == -1) {
|
|
idx = paneList.indexOf(tab.name);
|
|
if (idx == -1) { //Load paneList from index
|
|
if (tab.isActive())
|
|
addToPaneList(tab);
|
|
}
|
|
else
|
|
paneList[idx] = tab;
|
|
}
|
|
}, plugin);
|
|
|
|
tabs.on("focusSync", function(e) {
|
|
var tab = e.tab;
|
|
if (!tab.loaded) return;
|
|
|
|
if (!cycleKeyPressed) {
|
|
var accessList = tab.pane.meta.accessList;
|
|
accessList.remove(tab);
|
|
accessList.unshift(tab);
|
|
accessList.changed = true;
|
|
|
|
addToPaneList(tab, true);
|
|
paneList.changed = true;
|
|
|
|
settings.save();
|
|
}
|
|
|
|
// @todo panel switch
|
|
if (tree.area && tree.area.activePanel == "tree"
|
|
&& settings.getBool('user/general/@revealfile')) {
|
|
revealtab(tab, true);
|
|
}
|
|
}, plugin);
|
|
tabs.on("tabAfterActivate", function(e) {
|
|
var tab = e.tab;
|
|
if (tab == tabs.focussedTab || !tab.loaded)
|
|
return;
|
|
|
|
if (!cycleKeyPressed) {
|
|
var accessList = tab.pane.meta.accessList;
|
|
accessList.remove(tab);
|
|
accessList.splice(1, 0, tab);
|
|
accessList.changed = true;
|
|
|
|
addToPaneList(tab, 2);
|
|
paneList.changed = true;
|
|
|
|
settings.save();
|
|
}
|
|
}, plugin);
|
|
|
|
apf.addEventListener("keydown", function(eInfo) {
|
|
if (eInfo.keyCode == 17 || eInfo.keyCode == 18) {
|
|
cycleKeyPressed = true;
|
|
}
|
|
});
|
|
|
|
apf.addEventListener("keyup", function(eInfo) {
|
|
if (eInfo.keyCode == 17 || eInfo.keyCode == 18) {
|
|
cycleKeyPressed = false;
|
|
|
|
if (dirtyNextTab) {
|
|
var tab = tabs.focussedTab;
|
|
var accessList = tab.pane.meta.accessList;
|
|
if (accessList[0] != tab) {
|
|
accessList.remove(tab);
|
|
accessList.unshift(tab);
|
|
accessList.changed = true;
|
|
settings.save();
|
|
}
|
|
|
|
dirtyNextTab = false;
|
|
}
|
|
if (dirtyNextPane) {
|
|
accessedPane = 0;
|
|
|
|
var tab = tabs.focussedTab;
|
|
if (paneList[accessedPane] != tab && tab) {
|
|
paneList.remove(tab);
|
|
paneList.unshift(tab);
|
|
|
|
paneList.changed = true;
|
|
settings.save();
|
|
}
|
|
|
|
dirtyNextPane = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
// tabs.addEventListener("aftersavedialogcancel", function(e) {
|
|
// if (!changedTabs)
|
|
// return;
|
|
|
|
// var i, l, tab;
|
|
// for (i = 0, l = changedTabs.length; i < l; i++) {
|
|
// tab = changedTabs[i];
|
|
// tab.removeEventListener("aftersavedialogclosed", arguments.callee);
|
|
// }
|
|
// });
|
|
|
|
createLayoutMenus();
|
|
}
|
|
|
|
/***** Methods *****/
|
|
|
|
function addToPaneList(tab, first) {
|
|
var pane = tab.pane, found;
|
|
paneList.every(function(tab) {
|
|
if (tab && tab.pane && tab.pane == pane) {
|
|
found = tab;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
if (found)
|
|
paneList.remove(found);
|
|
|
|
if (first == 2)
|
|
paneList.splice(1, 0, tab);
|
|
else if (first)
|
|
paneList.unshift(tab);
|
|
else
|
|
paneList.push(tab);
|
|
}
|
|
|
|
function accessListToJson() {
|
|
var list = [];
|
|
this.forEach(function(tab, i) {
|
|
if (tab && tab.name)
|
|
list.push(tab.name);
|
|
});
|
|
return list;
|
|
}
|
|
|
|
function clonetab(tab) {
|
|
if (!tab)
|
|
tab = mnuContext.$tab || tabs.focussedTab;
|
|
|
|
var pane;
|
|
tabs.getTabs().every(function(tab) {
|
|
if (tab.document.meta.cloned) {
|
|
pane = tab.pane;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
if (!pane || pane == tab.pane)
|
|
pane = tab.pane.hsplit(true);
|
|
|
|
tabs.clone(tab, pane, function(err, tab) {
|
|
|
|
});
|
|
}
|
|
|
|
function closetab(tab) {
|
|
if (!tab)
|
|
tab = mnuContext.$tab || tabs.focussedTab;
|
|
|
|
var pages = tabs.getTabs();
|
|
var isLast = pages[pages.length - 1] == tab;
|
|
|
|
tab.close();
|
|
tabs.resizePanes(isLast);
|
|
|
|
return false;
|
|
}
|
|
|
|
function closealltabs(callback) {
|
|
callback = typeof callback == "function" ? callback : null;
|
|
|
|
changedTabs = [];
|
|
unchangedTabs = [];
|
|
|
|
var pages = tabs.getTabs();
|
|
for (var i = 0, l = pages.length; i < l; i++) {
|
|
closepage(pages[i], callback);
|
|
}
|
|
|
|
checkTabRender(callback);
|
|
}
|
|
|
|
function closeallbutme(me, pages, callback) {
|
|
if (!me) {
|
|
me = mnuContext.$tab || tabs.focussedTab;
|
|
}
|
|
|
|
changedTabs = [];
|
|
unchangedTabs = [];
|
|
|
|
if (!pages || !(pages instanceof Array)) {
|
|
var container = me && me.aml && me.aml.parentNode || tabs.container;
|
|
pages = tabs.getTabs(container);
|
|
}
|
|
var tab;
|
|
for (var i = 0, l = pages.length; i < l; i++) {
|
|
tab = pages[i];
|
|
if (tab !== me)
|
|
closepage(tab, callback);
|
|
}
|
|
|
|
tabs.resizePanes();
|
|
checkTabRender(callback);
|
|
}
|
|
|
|
function closepage(tab, callback) {
|
|
var doc = tab.document;
|
|
if (doc.changed && (!doc.meta.newfile || doc.value))
|
|
changedTabs.push(tab);
|
|
else
|
|
unchangedTabs.push(tab);
|
|
}
|
|
|
|
function checkTabRender(callback) {
|
|
save.saveAllInteractive(changedTabs, function(result) {
|
|
if (result != save.CANCEL) {
|
|
changedTabs.forEach(function(tab) {
|
|
tab.unload();
|
|
});
|
|
closeUnchangedTabs(done);
|
|
}
|
|
else
|
|
done();
|
|
});
|
|
function done() {
|
|
if (callback) callback();
|
|
// todo dialog calls this twice when selecting no with changed tab
|
|
setTimeout(function() {
|
|
changedTabs = [];
|
|
unchangedTabs = [];
|
|
});
|
|
}
|
|
}
|
|
|
|
function closeUnchangedTabs(callback) {
|
|
var tab;
|
|
for (var i = 0, l = unchangedTabs.length; i < l; i++) {
|
|
tab = unchangedTabs[i];
|
|
tab.close(true);
|
|
}
|
|
|
|
if (callback)
|
|
callback();
|
|
}
|
|
|
|
function closealltotheright(tab) {
|
|
if (!tab)
|
|
tab = mnuContext.$tab || tabs.focussedTab;
|
|
|
|
var pages = tab.pane.getTabs();
|
|
var currIdx = pages.indexOf(tab);
|
|
|
|
closeallbutme(tab, pages.slice(currIdx));
|
|
}
|
|
|
|
function closealltotheleft(tab) {
|
|
if (!tab)
|
|
tab = mnuContext.$tab || tabs.focussedTab;
|
|
|
|
var pages = tab.pane.getTabs();
|
|
var currIdx = pages.indexOf(tab);
|
|
|
|
closeallbutme(tab, pages.slice(0, currIdx));
|
|
}
|
|
|
|
function nexttab(dir, keepOrder) {
|
|
if (tabs.getTabs().length === 1)
|
|
return;
|
|
|
|
var tab = tabs.focussedTab;
|
|
var accessList = tab.pane.meta.accessList;
|
|
|
|
var index = accessList.indexOf(tab);
|
|
index += dir || 1;
|
|
|
|
if (index >= accessList.length)
|
|
index = 0;
|
|
else if (index < 0)
|
|
index = accessList.length - 1;
|
|
|
|
var next = accessList[index];
|
|
if (typeof next != "object" || !next.pane.visible)
|
|
return nexttab(dir, keepOrder);
|
|
if (keepOrder && cycleKeyPressed == false) {
|
|
cycleKeyPressed = true;
|
|
tabs.focusTab(next, null, true);
|
|
cycleKeyPressed = false;
|
|
} else {
|
|
tabs.focusTab(next, null, true);
|
|
}
|
|
dirtyNextTab = !keepOrder;
|
|
}
|
|
|
|
function previoustab(dir, keepOrder) {
|
|
nexttab(dir || -1, keepOrder)
|
|
}
|
|
|
|
function nextpane() {
|
|
return $nextPane(1);
|
|
}
|
|
|
|
function previouspane() {
|
|
return $nextPane(-1);
|
|
}
|
|
|
|
function $nextPane(dir) {
|
|
if (tabs.getPanes().length === 1)
|
|
return;
|
|
|
|
var l = paneList.length;
|
|
for (var i = 1; i <= l; i++) {
|
|
var index = (accessedPane + dir * i) % l;
|
|
var next = paneList[index];
|
|
if (typeof next != "object" || !next.pane.visible)
|
|
continue;
|
|
if (next.pane.activeTab == tabs.focussedTab) {
|
|
console.error("error in panelist");
|
|
continue;
|
|
}
|
|
|
|
accessedPane = index;
|
|
tabs.focusTab(next.pane.activeTab, null, true);
|
|
dirtyNextPane = true;
|
|
return next.pane;
|
|
}
|
|
}
|
|
|
|
function gotopaneleft() {
|
|
return $goToPane("left");
|
|
}
|
|
|
|
function gotopaneright() {
|
|
return $goToPane("right");
|
|
}
|
|
|
|
function gotopanedown() {
|
|
return $goToPane("down");
|
|
}
|
|
|
|
function gotopaneup() {
|
|
return $goToPane("up");
|
|
}
|
|
|
|
function $goToPane(direction) {
|
|
var newPane = findPaneToGoTo(direction);
|
|
if (!newPane) return;
|
|
|
|
var activeTab = newPane.activeTab;
|
|
tabs.focusTab(activeTab);
|
|
}
|
|
|
|
function getPaneDimensions(pane) {
|
|
var element = pane.container;
|
|
var size = getElementSize(element);
|
|
var dimensions = {
|
|
x: getElementOffset(element, "Left"),
|
|
y: getElementOffset(element, "Top"),
|
|
width: size.width,
|
|
height: size.height
|
|
};
|
|
return dimensions;
|
|
|
|
}
|
|
|
|
function getElementOffset(element, type) {
|
|
var offset = 0;
|
|
do {
|
|
if (!isNaN(element['offset' + type]))
|
|
{
|
|
offset += element['offset' + type];
|
|
}
|
|
} while (element = element.offsetParent);
|
|
return offset;
|
|
}
|
|
|
|
function getElementSize(element) {
|
|
var computedStyle = window.getComputedStyle(element);
|
|
return {
|
|
width: parseInt(computedStyle.width, 10),
|
|
height: parseInt(computedStyle.height, 10),
|
|
};
|
|
}
|
|
|
|
/** For each direction
|
|
* Exclude all panes not in the direction of this one
|
|
* Exclude all panes that don't intersect on the other axis
|
|
* Choose the closest pane
|
|
* In case of tie choose the pane to the furthest left or top.
|
|
**/
|
|
|
|
function findBoxToGoTo(boxes, currentBox, direction) {
|
|
var possibleBoxes = [];
|
|
|
|
switch (direction) {
|
|
case "left":
|
|
possibleBoxes = boxes
|
|
.filter(function (box) { return box.x < currentBox.x; })
|
|
.filter(areBoxesInLineVertically.bind(null, currentBox));
|
|
|
|
if (!possibleBoxes.length) return null;
|
|
|
|
var chosenBox = possibleBoxes.reduce(function (prev, cur) {
|
|
if (!prev || cur.x > prev.x) return cur;
|
|
if (cur.x == prev.x && cur.y < prev.y) return cur;
|
|
return prev;
|
|
});
|
|
|
|
return chosenBox;
|
|
break;
|
|
case "right":
|
|
possibleBoxes = boxes
|
|
.filter(function (box) { return box.x > currentBox.x; })
|
|
.filter(areBoxesInLineVertically.bind(null, currentBox));
|
|
|
|
if (!possibleBoxes.length) return null;
|
|
|
|
var chosenBox = possibleBoxes.reduce(function (prev, cur) {
|
|
if (!prev || cur.x < prev.x) return cur;
|
|
if (cur.x == prev.x && cur.y < prev.y) return cur;
|
|
return prev;
|
|
});
|
|
|
|
return chosenBox;
|
|
break;
|
|
case "up":
|
|
possibleBoxes = boxes
|
|
.filter(function (box) { return box.y < currentBox.y; })
|
|
.filter(areBoxesInLineHorizontally.bind(null, currentBox));
|
|
|
|
if (!possibleBoxes.length) return null;
|
|
|
|
var chosenBox = possibleBoxes.reduce(function (prev, cur) {
|
|
if (!prev || cur.y > prev.y) return cur;
|
|
if (cur.y == prev.y && cur.x < prev.x) return cur;
|
|
return prev;
|
|
});
|
|
|
|
return chosenBox;
|
|
break;
|
|
case "down":
|
|
possibleBoxes = boxes
|
|
.filter(function (box) { return box.y > currentBox.y; })
|
|
.filter(areBoxesInLineHorizontally.bind(null, currentBox));
|
|
|
|
if (!possibleBoxes.length) return null;
|
|
|
|
var chosenBox = possibleBoxes.reduce(function (prev, cur) {
|
|
if (!prev || cur.y < prev.y) return cur;
|
|
if (cur.y == prev.y && cur.x < prev.x) return cur;
|
|
return prev;
|
|
});
|
|
|
|
return chosenBox;
|
|
break;
|
|
}
|
|
}
|
|
|
|
function areBoxesInLineVertically(box1, box2) {
|
|
return !(box1.y + box1.height < box2.y || box2.y + box2.height < box1.y);
|
|
}
|
|
|
|
function areBoxesInLineHorizontally(box1, box2) {
|
|
return !(box1.x + box1.width < box2.x || box2.x + box2.width < box1.x);
|
|
}
|
|
|
|
|
|
function findPaneToGoTo(direction) {
|
|
var panes = tabs.getPanes();
|
|
if (!tabs.focussed || !tabs.focussedTab)
|
|
return;
|
|
|
|
var currentPane = tabs.focussedTab.pane;
|
|
if (!currentPane) return;
|
|
|
|
var boxes = panes.map(function (pane) {
|
|
return getPaneDimensions(pane);
|
|
});
|
|
var currentBox = getPaneDimensions(currentPane);
|
|
|
|
var newBox = findBoxToGoTo(boxes, currentBox, direction);
|
|
if (!newBox) return;
|
|
|
|
var newPane = null;
|
|
panes.forEach(function (pane) {
|
|
var paneDimensions = getPaneDimensions(pane);
|
|
if (paneDimensions.x == newBox.x && paneDimensions.y == newBox.y) {
|
|
newPane = pane;
|
|
}
|
|
});
|
|
|
|
return newPane;
|
|
}
|
|
|
|
function gototabright(opts) {
|
|
return cycleTab("right", opts);
|
|
}
|
|
|
|
function gototableft(opts) {
|
|
return cycleTab("left", opts);
|
|
}
|
|
|
|
function cycleTab(dir, opts) {
|
|
var curr = tabs.focussedTab;
|
|
var pages = curr && curr.pane.getTabs();
|
|
if (!pages || pages.length == 1)
|
|
return;
|
|
|
|
if (opts && opts.editorType) {
|
|
pages = pages.filter(function(p) {
|
|
return p.editorType == opts.editorType;
|
|
});
|
|
}
|
|
|
|
var currIdx = pages.indexOf(curr);
|
|
var start = currIdx;
|
|
var tab;
|
|
|
|
do {
|
|
var idx = currIdx;
|
|
switch (dir) {
|
|
case "right": idx++; break;
|
|
case "left": idx--; break;
|
|
case "first": idx = 0; break;
|
|
case "last": idx = pages.length - 1; break;
|
|
default: idx--;
|
|
}
|
|
|
|
if (idx < 0)
|
|
idx = pages.length - 1;
|
|
if (idx > pages.length - 1)
|
|
idx = 0;
|
|
|
|
// No pages found that can be focussed
|
|
if (start == idx)
|
|
return;
|
|
|
|
tab = pages[idx];
|
|
}
|
|
while (!tab.pane.visible);
|
|
|
|
if (tab.pane.visible)
|
|
tabs.focusTab(tab, null, true);
|
|
|
|
return false;
|
|
}
|
|
|
|
function movetabright() { hmoveTab("right"); }
|
|
function movetableft() { hmoveTab("left"); }
|
|
function movetabup() { vmoveTab("up"); }
|
|
function movetabdown() { vmoveTab("down"); }
|
|
|
|
function hmoveTab(dir) {
|
|
var bRight = dir == "right";
|
|
var tab = tabs.focussedTab;
|
|
if (!tab)
|
|
return;
|
|
|
|
// Tabs within the current pane
|
|
var pages = tab.pane.getTabs();
|
|
|
|
// Get new index
|
|
var idx = pages.indexOf(tab) + (bRight ? 2 : -1);
|
|
|
|
// Before current pane
|
|
if (idx < 0 || idx > pages.length) {
|
|
tab.pane.moveTabToSplit(tab, dir);
|
|
}
|
|
// In current pane
|
|
else {
|
|
tab.attachTo(tab.pane, pages[idx], null, true);
|
|
}
|
|
|
|
tabs.focusTab(tab);
|
|
return false;
|
|
}
|
|
|
|
function vmoveTab(dir) {
|
|
var tab = tabs.focussedTab;
|
|
if (!tab)
|
|
return;
|
|
|
|
tab.pane.moveTabToSplit(tab, dir);
|
|
tabs.focusTab(tab);
|
|
return false;
|
|
}
|
|
|
|
function tab1() { return showTab(1); }
|
|
function tab2() { return showTab(2); }
|
|
function tab3() { return showTab(3); }
|
|
function tab4() { return showTab(4); }
|
|
function tab5() { return showTab(5); }
|
|
function tab6() { return showTab(6); }
|
|
function tab7() { return showTab(7); }
|
|
function tab8() { return showTab(8); }
|
|
function tab9() { return showTab(9); }
|
|
function tab0() { return showTab(10); }
|
|
|
|
function showTab(idx) {
|
|
// our indexes are 0 based an the number coming in is 1 based
|
|
var pages = [];
|
|
tabs.getPanes().forEach(function(pane) {
|
|
pages = pages.concat(pane.getTabs());
|
|
});
|
|
pages = pages.filter(function(tab) {
|
|
return tab.title;
|
|
});
|
|
var tab = pages[idx - 1];
|
|
if (!tab)
|
|
return false;
|
|
|
|
tabs.focusTab(tab, null, true);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Scrolls to the selected pane's file path in the "Project Files" tree
|
|
*
|
|
* Works by Finding the node related to the active pane in the tree, and
|
|
* unfolds its parent folders until the node can be reached by an xpath
|
|
* selector and focused, to finally scroll to the selected node.
|
|
*/
|
|
function revealtab(tab, noFocus) {
|
|
if (!tab || tab.command)
|
|
tab = tabs.focussedTab;
|
|
if (!tab)
|
|
return false;
|
|
|
|
// Tell other extensions to exit their fullscreen mode (for ex. Zen)
|
|
// so this operation is visible
|
|
// ide.dispatchEvent("exitfullscreen");
|
|
|
|
revealInTree(tab, noFocus);
|
|
}
|
|
|
|
function revealInTree(tab, noFocus) {
|
|
panels.activate("tree");
|
|
var path = tab.path || tab.relatedPath;
|
|
|
|
if (path)
|
|
done(null, path);
|
|
else if (tab.editor.getPathAsync)
|
|
tab.editor.getPathAsync(done);
|
|
|
|
if (!noFocus)
|
|
tree.focus();
|
|
|
|
function done(err, path) {
|
|
if (err || !path)
|
|
return console.error(err);
|
|
tree.expand(path, function(err) {
|
|
if (!err)
|
|
tree.select(path);
|
|
tree.scrollToSelection();
|
|
});
|
|
}
|
|
}
|
|
|
|
function canTabBeRemoved(pane, min) {
|
|
if (!pane || pane.getTabs().length > (min || 0))
|
|
return false;
|
|
|
|
var containers = tabs.containers;
|
|
for (var i = 0; i < containers.length; i++) {
|
|
if (ui.isChildOf(containers[i], pane.aml)) {
|
|
return containers[i]
|
|
.getElementsByTagNameNS(apf.ns.aml, "tab").length > 1;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function closepane(tab, pane) {
|
|
if (!tab)
|
|
tab = tabs.focussedTab;
|
|
if (!pane)
|
|
pane = tab.pane;
|
|
if (!pane) return;
|
|
|
|
var pages = pane.getTabs();
|
|
if (!pages.length) {
|
|
if (canTabBeRemoved(pane))
|
|
pane.unload();
|
|
return;
|
|
}
|
|
|
|
changedTabs = [];
|
|
unchangedTabs = [];
|
|
|
|
// Ignore closing tabs
|
|
menuClosedItems.ignore = true;
|
|
|
|
// Keep information to restore pane set
|
|
var state = [];
|
|
var type = pane.aml.parentNode.localName;
|
|
var nodes = pane.aml.parentNode.childNodes.filter(function(p) {
|
|
return p.localName != "splitter";
|
|
});
|
|
|
|
state.title = pages.length + " Tabs";
|
|
state.type = type == "vsplitbox" ? "vsplit" : "hsplit";
|
|
state.far = nodes.indexOf(pane.aml) == 1;
|
|
state.sibling = nodes[state.far ? 0 : 1];
|
|
state.getState = function() { return state; };
|
|
state.restore = $restoreTabGroup;
|
|
state.paneName = pane.name;
|
|
state.document = { meta: {}};
|
|
|
|
// Close pages
|
|
pages.forEach(function(tab) {
|
|
state.push(tab.getState());
|
|
closepage(tab);
|
|
});
|
|
|
|
tabs.resizePanes();
|
|
checkTabRender(function() {
|
|
if (canTabBeRemoved(pane))
|
|
pane.unload();
|
|
|
|
// Stop ignoring closing tabs
|
|
menuClosedItems.ignore = false;
|
|
|
|
// @todo there should probably be some check here
|
|
addTabToClosedMenu(state);
|
|
});
|
|
}
|
|
|
|
function $restoreTabGroup(state) {
|
|
// pane was not being used. Why?
|
|
// var pane = state.sibling;
|
|
// if (pane && pane.cloud9pane)
|
|
// pane = pane.cloud9pane.aml;
|
|
var pane = tabs.findPane(state.paneName) || {};
|
|
var oldpane = state.pane;
|
|
var newpane = oldpane.getTabs().length === 0
|
|
? oldpane
|
|
: oldpane[state.type](state.far, null, pane.aml);
|
|
|
|
state.forEach(function(s) {
|
|
s.pane = newpane;
|
|
tabs.open(s, function() {});
|
|
});
|
|
}
|
|
|
|
function hsplit(tab, pane) {
|
|
if (!tab)
|
|
tab = tabs.focussedTab;
|
|
|
|
if (tab)
|
|
pane = tab.pane;
|
|
|
|
var newpane = pane.hsplit(true);
|
|
if (pane.getTabs().length > 1)
|
|
tab.attachTo(newpane);
|
|
}
|
|
|
|
function vsplit(tab, pane) {
|
|
if (!tab)
|
|
tab = tabs.focussedTab;
|
|
|
|
if (tab)
|
|
pane = tab.pane;
|
|
|
|
var newpane = pane.vsplit(true);
|
|
if (pane.getTabs().length > 1)
|
|
tab.attachTo(newpane);
|
|
}
|
|
|
|
function nosplit() {
|
|
var panes = tabs.getPanes(tabs.container);
|
|
var first = panes[0];
|
|
for (var pane, i = 1, li = panes.length; i < li; i++) {
|
|
var pages = (pane = panes[i]).getTabs();
|
|
for (var j = 0, lj = pages.length; j < lj; j++) {
|
|
pages[j].attachTo(first, null, true);
|
|
}
|
|
pane.unload();
|
|
}
|
|
}
|
|
|
|
function twovsplit(hsplit) {
|
|
var panes = tabs.getPanes(tabs.container);
|
|
|
|
// We're already in a two vsplit
|
|
if (panes.length == 2 && panes[0].aml.parentNode.localName
|
|
== (hsplit ? "hsplitbox" : "vsplitbox"))
|
|
return panes;
|
|
|
|
// Split the only pane there is
|
|
if (panes.length == 1) {
|
|
var newtab = panes[0][hsplit ? "hsplit" : "vsplit"](true);
|
|
return [panes[0], newtab];
|
|
}
|
|
|
|
var c = tabs.containers[0].firstChild.childNodes.filter(function(f) {
|
|
return f.localName != "splitter";
|
|
});
|
|
// var left = c[0].getElementsByTagNameNS(apf.ns.aml, "tab");
|
|
var right = c[1].getElementsByTagNameNS(apf.ns.aml, "tab");
|
|
|
|
for (var i = 1, l = panes.length; i < l; i++) {
|
|
panes[i].unload();
|
|
}
|
|
|
|
var newtab = panes[0][hsplit ? "hsplit" : "vsplit"](true);
|
|
right.forEach(function(tab) {
|
|
if (tab.cloud9tab)
|
|
tab.cloud9tab.attachTo(newtab, null, true);
|
|
});
|
|
|
|
return [panes[0], newtab];
|
|
}
|
|
|
|
function twohsplit() {
|
|
return twovsplit(true);
|
|
}
|
|
|
|
function foursplit() {
|
|
var panes = twohsplit();
|
|
panes[0].vsplit(true);
|
|
panes[1].vsplit(true);
|
|
}
|
|
|
|
function threeleft() {
|
|
var panes = twohsplit();
|
|
panes[0].vsplit(true);
|
|
}
|
|
|
|
function threeright() {
|
|
var panes = twohsplit();
|
|
panes[1].vsplit(true);
|
|
}
|
|
|
|
function checkReopenedTab(e) {
|
|
var tab = e.tab;
|
|
if (!tab.path)
|
|
return;
|
|
|
|
fs.stat(tab.path, function(err, stat) {
|
|
if (err) return;
|
|
|
|
// @todo this won't work well on windows, because
|
|
// there is a 20s period in which the mtime is
|
|
// the same. The solution would be to have a
|
|
// way to compare the saved document to the
|
|
// loaded document that created the state
|
|
if (tab.document.meta.timestamp < stat.mtime) {
|
|
var doc = tab.document;
|
|
|
|
question("File Changed",
|
|
tab.path + " has been changed on disk.",
|
|
"Would you like to reload this file?",
|
|
function() {
|
|
tabs.reload(tab, function() {});
|
|
},
|
|
function() {
|
|
// Set to changed
|
|
doc.undoManager.bookmark(-2);
|
|
},
|
|
{ merge: false, all: false }
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Record the last 10 closed tabs or pane sets
|
|
function addTabToClosedMenu(tab) {
|
|
if (menuClosedItems.ignore) return;
|
|
if (tab.document.meta.preview || tab.document.meta.cloned)
|
|
return;
|
|
|
|
// Record state
|
|
var state = tab.getState();
|
|
var restore = tab.restore;
|
|
|
|
var path = tab.path || tab.editorType;
|
|
if (!restore) {
|
|
for (var i = menuClosedItems.length - 1; i >= 0; i--) {
|
|
if (menuClosedItems[i].path == path) {
|
|
menuClosedItems.splice(i, 1)[0].destroy(true, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create menu item
|
|
var item = new ui.item({
|
|
caption: tab.title,
|
|
path: path,
|
|
style: "padding-left:35px",
|
|
onclick: function(e) {
|
|
// Update State
|
|
state.active = true;
|
|
state.pane = this.parentNode.pane;
|
|
|
|
tabs.on("open", checkReopenedTab);
|
|
|
|
// Open pane
|
|
restore
|
|
? restore(state)
|
|
: tabs.open(state, function() {});
|
|
|
|
tabs.off("open", checkReopenedTab);
|
|
|
|
// Remove pane from menu
|
|
menuClosedItems.remove(item);
|
|
item.destroy(true, true);
|
|
|
|
// Clear label and divider if there are no items
|
|
if (menuClosedItems.length === 0)
|
|
menuClosedItems.hide();
|
|
}
|
|
});
|
|
|
|
// TODO: passing path to item doesn't work since apf adds it only when menu is shown
|
|
item.path = path;
|
|
|
|
// Add item to menu
|
|
menuClosedItems.push(item);
|
|
var index = menuClosedItems.index = (menuClosedItems.index || 0) + 1;
|
|
menus.addItemToMenu(mnuEditors, item, 2000000 - index, false);
|
|
|
|
// Show label and divider
|
|
menuClosedItems.show();
|
|
|
|
// Remove excess menu item
|
|
if (menuClosedItems.length > 10)
|
|
menuClosedItems.shift().destroy(true, true);
|
|
tab = null;
|
|
}
|
|
|
|
function updateTabMenu(force) {
|
|
// Approximating order
|
|
var pages = [];
|
|
tabs.getPanes().forEach(function(pane) {
|
|
pages = pages.concat(pane.getTabs());
|
|
});
|
|
var length = Math.min(10, pages.length);
|
|
var start = 1000;
|
|
|
|
// Destroy all items
|
|
menuItems.forEach(function(item) {
|
|
item.destroy(true, true);
|
|
});
|
|
menuItems = [];
|
|
|
|
if (!pages.length)
|
|
return;
|
|
|
|
var mnu, tab;
|
|
|
|
// Create new divider
|
|
menus.addItemToMenu(mnuTabs, mnu = new ui.divider(), start, false);
|
|
menuItems.push(mnu);
|
|
|
|
// Create new items
|
|
for (var i = 0; i < length; i++) {
|
|
tab = pages[i];
|
|
if (!tab.title) continue;
|
|
menus.addItemToMenu(mnuTabs, mnu = new ui.item({
|
|
caption: tab.title.replace(/[/]/g, "\u2044"),
|
|
relPage: tab,
|
|
command: "tab" + (i == 9 ? 0 : i + 1)
|
|
}), start + i + 1, false);
|
|
menuItems.push(mnu);
|
|
}
|
|
|
|
if (pages.length > length) {
|
|
menus.addItemToMenu(mnuTabs, mnu = new ui.item({
|
|
caption: "More...",
|
|
onclick: function() {
|
|
commands.exec("toggleOpenfiles", null, { forceOpen: true });
|
|
}
|
|
}), start + length + 1, false);
|
|
menuItems.push(mnu);
|
|
}
|
|
tab = pages = null;
|
|
}
|
|
|
|
function reopenLastTab() {
|
|
var item = menuClosedItems[menuClosedItems.length - 1];
|
|
if (item)
|
|
item.getAttribute("onclick").call(item);
|
|
}
|
|
|
|
function createLayoutMenus() {
|
|
var LAYOUT_MENU_PATH = "Window/Saved Layouts/";
|
|
var SAVED_LAYOUTS_PATH = "/.c9/saved-layouts/";
|
|
|
|
commands.addCommand({
|
|
name: "savePaneLayout",
|
|
group: "Window",
|
|
bindKey: {},
|
|
exec: function (editor, args) {
|
|
var state = tabs.getState(null, true);
|
|
var stateName = prompt("Name your layout", getAutoSaveName());
|
|
if (!stateName)
|
|
return;
|
|
var sanitizedStateName = stateName.trim().replace(/[\\\/:\r\n~]|\.\./g, "-") + ".tabstate";
|
|
|
|
fs.writeFile(SAVED_LAYOUTS_PATH + sanitizedStateName, JSON.stringify(state, null, "\t"), function(err) {
|
|
if (err) {
|
|
return alert(err);
|
|
}
|
|
});
|
|
}
|
|
}, plugin);
|
|
|
|
commands.addCommand({
|
|
name: "savePaneLayoutAndCloseTabs",
|
|
group: "Window",
|
|
bindKey: {},
|
|
exec: function (editor, args) {
|
|
commands.exec("savePaneLayout");
|
|
tabs.setState(null, function() {});
|
|
}
|
|
}, plugin);
|
|
|
|
// menus.insert
|
|
menus.addItemByPath(LAYOUT_MENU_PATH, new ui.menu({
|
|
"onprop.visible": function(e) {
|
|
if (e.value) {
|
|
rebuildLayoutMenu();
|
|
fs.readdir(SAVED_LAYOUTS_PATH, function(err, files) {
|
|
rebuildLayoutMenu(err, files);
|
|
});
|
|
}
|
|
},
|
|
"onitemclick": function(e) {
|
|
var stat = e.relatedNode && e.relatedNode.value;
|
|
if (stat && stat.name) {
|
|
fs.readFile(SAVED_LAYOUTS_PATH + stat.name, function(err, contents) {
|
|
if (err) return alert(err);
|
|
try {
|
|
contents = JSON.parse(contents);
|
|
}
|
|
catch (e) {
|
|
return alert(e);
|
|
}
|
|
tabs.setState(null, function() {});
|
|
tabs.setState(contents, function(err) {
|
|
if (err) return alert(err);
|
|
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}), 10050, plugin);
|
|
|
|
function getAutoSaveName() {
|
|
return (new Date()).toLocaleString() + " [" + tabs.getTabs().length + " tabs]";
|
|
}
|
|
|
|
function rebuildLayoutMenu(err, stats) {
|
|
menus.remove(LAYOUT_MENU_PATH);
|
|
var c = 0;
|
|
|
|
menus.addItemByPath(LAYOUT_MENU_PATH + "Save...", new ui.item({
|
|
command: "savePaneLayout"
|
|
}), c += 100, plugin);
|
|
|
|
menus.addItemByPath(LAYOUT_MENU_PATH + "Save And Close All...", new ui.item({
|
|
command: "savePaneLayoutAndCloseTabs"
|
|
}), c += 100, plugin);
|
|
|
|
menus.addItemByPath(LAYOUT_MENU_PATH + "~", new ui.divider({
|
|
}), c += 100, plugin);
|
|
|
|
menus.addItemByPath(LAYOUT_MENU_PATH + "Show Saved Layouts in File Tree", new ui.item({
|
|
onclick: function() {
|
|
revealInTree({ path: SAVED_LAYOUTS_PATH });
|
|
}
|
|
}), c += 100, plugin);
|
|
|
|
menus.addItemByPath(LAYOUT_MENU_PATH + "~", new ui.divider({
|
|
}), c += 100, plugin);
|
|
|
|
if (err) {
|
|
if (err.code == "ENOENT")
|
|
return;
|
|
return menus.addItemByPath(LAYOUT_MENU_PATH + "Error loading saved layouts", new ui.item({
|
|
disabled: true,
|
|
}), c += 100, plugin);
|
|
}
|
|
else if (!stats) {
|
|
return menus.addItemByPath(LAYOUT_MENU_PATH + "loading...", new ui.item({
|
|
disabled: true,
|
|
}), c += 100, plugin);
|
|
}
|
|
|
|
for (var i = 0; i < stats.length; i++) {
|
|
var stat = stats[i];
|
|
var caption = stat.name.replace(/.tabstate$/, "");
|
|
menus.addItemByPath(LAYOUT_MENU_PATH + caption, new ui.item({
|
|
value: stat,
|
|
}), c += 100, plugin);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***** Lifecycle *****/
|
|
|
|
plugin.on("load", function() {
|
|
load();
|
|
});
|
|
plugin.on("enable", function() {
|
|
|
|
});
|
|
plugin.on("disable", function() {
|
|
|
|
});
|
|
plugin.on("unload", function() {
|
|
menuItems.forEach(function(item) {
|
|
item.destroy(true, true);
|
|
});
|
|
menuItems = [];
|
|
menuClosedItems.forEach(function(item) {
|
|
item.destroy(true, true);
|
|
});
|
|
menuClosedItems.length = 0; // = [];
|
|
|
|
mnuContext = null;
|
|
mnuEditors = null;
|
|
mnuTabs = null;
|
|
paneList = null;
|
|
accessedPane = null;
|
|
cycleKeyPressed = null;
|
|
changedTabs = null;
|
|
unchangedTabs = null;
|
|
dirtyNextTab = null;
|
|
dirtyNextPane = null;
|
|
|
|
loaded = false;
|
|
});
|
|
|
|
/***** Register and define API *****/
|
|
|
|
/**
|
|
* Draws the file tree
|
|
* @event afterfilesave Fires after a file is saved
|
|
* @param {Object} e
|
|
* node {XMLNode} description
|
|
* oldpath {String} description
|
|
**/
|
|
plugin.freezePublicAPI({
|
|
/**
|
|
*
|
|
*/
|
|
get contextMenu() { return mnuContext; },
|
|
|
|
/**
|
|
*
|
|
*/
|
|
clonetab: clonetab,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
closetab: closetab,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
closealltabs: closealltabs,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
closeallbutme: closeallbutme,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
gototabright: gototabright,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
gototableft: gototableft,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
movetabright: movetabright,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
movetableft: movetableft,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
movetabup: movetabup,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
movetabdown: movetabdown,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
tab1: tab1,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
tab2: tab2,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
tab3: tab3,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
tab4: tab4,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
tab5: tab5,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
tab6: tab6,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
tab7: tab7,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
tab8: tab8,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
tab9: tab9,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
tab0: tab0,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
revealtab: revealtab,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
reopenLastTab: reopenLastTab,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
nexttab: nexttab,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
previoustab: previoustab,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
closealltotheright: closealltotheright,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
closealltotheleft: closealltotheleft,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
closepane: closepane,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
hsplit: hsplit,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
vsplit: vsplit,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
nosplit: nosplit,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
twovsplit: twovsplit,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
twohsplit: twohsplit,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
foursplit: foursplit,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
threeleft: threeleft,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
threeright: threeright,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
nextpane: nextpane,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
previouspane: previouspane,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
gotopaneleft: gotopaneleft,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
gotopaneright: gotopaneright,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
gotopanedown: gotopanedown,
|
|
|
|
/**
|
|
*
|
|
*/
|
|
gotopaneup: gotopaneup,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* @ignore
|
|
*/
|
|
cycleTab: cycleTab
|
|
});
|
|
|
|
register(null, {
|
|
tabbehavior: plugin
|
|
});
|
|
}
|
|
});
|