Caret/js/sessions.js
Thomas Wilburn 2d68149cc5 .
2014-02-23 12:30:30 -08:00

384 lines
No EOL
10 KiB
JavaScript

define([
"sessions/state",
"editor",
"ui/dialog",
"ui/contextMenus",
"command",
"tab",
"storage/settingsProvider",
"ui/statusbar",
"util/manos",
"aceBindings"
],
function(state, editor, dialog, contextMenus, command, Tab, Settings, status, M) {
/*
The sessions module manages the presentation and rendering of the editor tabs.
It's probably overcomplicated, and I'm slowly moving chunks of it out into
other, more appropriate modules.
Part of this move is to transfer the tab state (the tabs and stack arrays) into
an external module, which can then be requested and manipulated by other modules
imported here.
*/
var syntax = document.find(".syntax");
var stackOffset = 0;
var renderTabs = function() {
var tabContainer = document.find(".tabs");
var contents = "";
var current = editor.getSession();
tabContainer.innerHTML = "";
state.tabs.forEach(function(tab, i) {
var element = tab.render(i);
if (tab === current) {
element.addClass("active");
}
tabContainer.append(element);
});
setTimeout(function() {
//wait for render before triggering the enter animation
tabContainer.findAll(".enter").forEach(function(element) { element.removeClass("enter") });
});
command.fire("session:retain-tabs");
};
command.on("session:render", renderTabs);
var addTab = function(contents, file) {
var current = editor.getSession();
var tab;
//reuse tab if opening a file into an empty tab
if (file && !current.file && !current.modified) {
tab = current;
tab.setValue(contents);
tab.setFile(file);
tab.modified = false;
} else {
tab = new Tab(contents, file);
state.stack.unshift(tab);
state.tabs.push(tab);
}
if (file && !file.virtual) {
file.entry.file(function(f) {
var loaded = ["Loaded ", f.name, ", ", f.size, " bytes"].join("");
status.toast(loaded, 2);
});
}
tab.detectSyntax();
raiseTab(tab);
return tab;
};
//removeTab looks long, but it handles the async save/don't/cancel flow
var removeTab = function(index, c) {
if (!index) {
index = state.tabs.indexOf(editor.getSession());
}
var tab = state.tabs[index];
state.stack = state.stack.filter(function(t) { return t !== tab });
var continuation = function() {
tab.drop();
state.tabs = state.tabs.filter(function(tab, i) {
if (i == index) {
return false;
}
return true;
});
if (tabs.length == 0) {
return addTab();
}
var next = index - 1;
if (next < 0) {
next = 0;
}
var current = editor.getSession();
if (tab !== current) {
renderTabs();
} else {
raiseTab(state.tabs[next]);
}
if (c) c();
};
if (tab.modified) {
dialog(
tab.fileName + " has been modified.\nDo you want to save changes?",
[
{label: "Save", value: true, shortcut: "y" },
{label: "Don't save", value: false, shortcut: "n" },
{ label: "Cancel", shortcut: "c" }
],
function(confirm) {
if (typeof confirm !== "boolean") {
return;
}
if (confirm) {
return tab.save(continuation);
}
continuation();
});
return;
} else {
continuation();
}
};
var raiseTab = function(tab) {
editor.setSession(tab);
syntax.value = tab.syntaxMode || "plain_text";
renderTabs();
editor.focus();
command.fire("session:check-file");
};
var raiseBlurred = function(tab) {
editor.setSession(tab);
syntax.value = tab.syntaxMode || "plain_text";
renderTabs();
command.fire("session:check-file");
};
var resetStack = function(tab) {
var raised = tab || state.stack[stackOffset];
state.stack = state.stack.filter(function(t) { return t != raised });
state.stack.unshift(raised);
}
var watchCtrl = function(e) {
if (e.keyCode == 17) {
resetStack();
document.body.off("keyup", watchCtrl);
ctrl = false;
}
};
var ctrl = false;
var switchTab = function(arg, c) {
arg = arg || 1;
if (!ctrl) {
ctrl = true;
stackOffset = 0;
document.body.on("keyup", watchCtrl);
}
stackOffset = (stackOffset + arg) % state.stack.length;
if (stackOffset < 0) stackOffset = state.stack.length + stackOffset;
raiseTab(state.stack[stackOffset]);
if (c) c();
};
var switchTabLinear = function(shift, c) {
shift = shift || 1;
var current = editor.getSession();
var currentIndex = state.tabs.indexOf(current);
var shifted = (currentIndex + shift) % state.tabs.length;
if (shifted < 0) {
shifted = state.tabs.length + shifted;
}
var tab = state.tabs[shifted];
raiseTab(tab);
resetStack(tab);
if (c) c();
};
command.on("session:raise-tab", function(index) {
var tab = state.tabs[index];
raiseTab(tab);
resetStack(tab);
});
command.on("session:close-tab", removeTab);
command.on("session:change-tab", switchTab);
command.on("session:change-tab-linear", switchTabLinear);
var enableTabDragDrop = function() {
var tabContainer = document.find(".tabs");
var draggedTab = null;
tabContainer.on("dragstart", function(e) {
if (!e.target.matches(".tab")) return;
e.target.style.opacity = .4;
setTimeout(function() {
e.target.addClass("dragging");
}, 50);
e.dataTransfer.setDragImage(e.target, 0, 0);
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.clearData("text/plain");
e.dataTransfer.clearData("text/uri-list");
e.dataTransfer.setData("application/x-tab-id", e.target.getAttribute("argument"));
draggedTab = e.target;
draggedTab.ondragend = function() {
draggedTab = null;
e.target.style.opacity = null;
e.target.removeClass("dragging");
};
});
tabContainer.on("dragover", function(e) {
e.preventDefault();
e.stopPropagation();
e.dropEffect = "move";
});
tabContainer.on("dragenter", function(e) {
if (!e.target.matches(".tab")) return;
e.target.addClass("hovering");
e.stopPropagation();
});
tabContainer.on("dragleave", function(e) {
if (!e.target.matches(".tab")) return;
e.target.removeClass("hovering");
});
tabContainer.on("drop", function(e) {
if (!draggedTab) return;
e.stopPropagation();
var target = e.target;
var location = "before";
var x = e.offsetX;
while (!target.matches(".tab")) {
if (target == tabContainer) {
var elements = tabContainer.findAll(".tab");
location = "after";
elements.forEach(function(el) {
if (el.offsetLeft < x) {
target = el;
}
});
break;
}
target = target.parentElement;
x += target.offsetLeft;
}
var from = state.tabs[e.dataTransfer.getData("application/x-tab-id") * 1];
var onto = state.tabs[target.getAttribute("argument") * 1];
if (from != onto) {
var reordered = [];
state.tabs.forEach(function(t) {
if (t == from) return;
if (t == onto && location == "before") {
reordered.push(from);
}
reordered.push(t);
if (t == onto && location == "after") {
reordered.push(from);
}
});
state.tabs = reordered;
}
renderTabs();
});
};
var enableTabMiddleClick = function() {
var tabContainer = document.find(".tabs");
tabContainer.on("click", function(e) {
if (!e.target.matches(".tab")) return;
if (e.button != 1) return;
e.preventDefault();
command.fire("session:close-tab", e.target.getAttribute("argument"));
});
};
var closeTabsRight = function(tabID) {
tabID = tabID || state.tabs.indexOf(editor.getSession());
var toClose = [];
for (var i = state.tabs.length - 1; i > tabID; i--) {
toClose.push(i);
}
M.serial(toClose, removeTab);
};
command.on("session:close-to-right", closeTabsRight);
contextMenus.register("Close", "closeTab", "tabs/:id", function(args) {
command.fire("session:close-tab", args.id);
});
contextMenus.register("Close tabs to the right", "closeTabsRight", "tabs/:id", function(args) {
closeTabsRight(args.id);
});
var init = function() {
Settings.pull("ace").then(function(data) {
data.ace.modes.forEach(function(mode) {
var option = document.createElement("option");
option.innerHTML = mode.label;
option.value = mode.name;
syntax.append(option);
});
})
if (!state.tabs.length) addTab("");
renderTabs();
enableTabDragDrop();
enableTabMiddleClick();
reset();
return "sessions";
};
var reset = function() {
state.tabs.forEach(function(tab) {
tab.detectSyntax();
});
};
command.on("init:startup", init);
command.on("init:restart", reset);
command.on("session:syntax", function(mode) {
var session = editor.getSession();
session.setMode("ace/mode/" + mode);
session.syntaxMode = mode;
syntax.value = mode;
editor.focus();
});
var locationMemory = null;
return {
addFile: addTab,
addDefaultsFile: function(name) {
var tab = addTab(Settings.getAsString(name, true));
tab.syntaxMode = "javascript";
tab.detectSyntax();
tab.fileName = name + ".json";
renderTabs();
},
getAllTabs: function() {
return state.tabs;
},
getCurrent: function() {
return editor.getSession();
},
getTabByIndex: function(index) {
return state.tabs[index];
},
getTabByName: function(name) {
for (var i = 0; i < state.tabs.length; i++) {
if (state.tabs[i].fileName == name) {
return state.tabs[i];
}
}
return null;
},
getFilenames: function() {
return state.tabs.map(function(t) { return t.fileName });
},
setCurrent: raiseTab,
raiseBlurred: raiseBlurred,
saveLocation: function() {
var session = editor.getSession();
var position = editor.getCursorPosition();
locationMemory = {
tab: session,
cursor: position
};
},
restoreLocation: function() {
if (!locationMemory) return;
raiseTab(locationMemory.tab);
editor.moveCursorToPosition(locationMemory.cursor);
},
renderTabs: renderTabs
}
});