MainViewManager manages the arrangement of all open panes as well as provides the controller logic behind all views in the MainView (e.g. ensuring that a file doesn't appear in 2 lists)
Each pane contains one or more views wich are created by a view factory and inserted into a pane list. There may be several panes managed by the MainViewManager with each pane containing a list of views. The panes are always visible and the layout is determined by the MainViewManager and the user.
Currently we support only 2 panes.
All of the WorkingSet APIs take a paneId Argument. This can be an actual pane Id, ALL_PANES (in most cases) or ACTIVE_PANE. ALL_PANES may not be supported for some APIs. See the API for details.
This module dispatches several events:
(e, newPaneId:string, oldPaneId:string)
(e, newFile:File, newPaneId:string, oldFile:File, oldPaneId:string)
(e, orientation:string)
(e, paneId:string)
(e, paneId:string)
To listen for working set changes, you must listen to all of these events:
(e, fileAdded:File, index:number, paneId:string)
(e, fileAdded:Array.<File>, paneId:string)
(e, File:FILE, sourcePaneId:string, destinationPaneId:string)
(e, fileRemoved:File, suppressRedraw:boolean, paneId:string)
(e, filesRemoved:Array.<File>, paneId:string)
(e, paneId:string)
listeners should discard all working set info and rebuilt it from the pane
by calling getWorkingSet()
(e, paneId:string)
(e, paneId:string) For Internal Use Only.
To listen for events, do something like this: (see EventDispatcher for details on this pattern)
MainViewManager.on("eventname", handler);
Special paneId shortcut that can be used to specify that the API should target the focused pane only. All APIs support this shortcut.
var ACTIVE_PANE = "ACTIVE_PANE";
Special paneId shortcut that can be used to specify that all panes should be targeted by the API. Not all APIs support this constnant. Check the API documentation before use.
var ALL_PANES = "ALL_PANES";
The minimum width or height that a pane can be
var MIN_PANE_SIZE = 75;
Legacy Preference setting name used to migrate old preferences
var OLD_PREFS_NAME = "project.files";
Preference setting name for the MainView Saved State
var PREFS_NAME = "mainView.state";
The global MRU list (for traversing)
var _mruList = [];
current orientation (null, VERTICAL or HORIZONTAL)
var _orientation = null;
map of pane scroll states
var _paneScrollStates = {};
localized pane titles
var _paneTitles = {};
flag indicating if traversing is currently taking place When True, changes the current pane's MRU list will not be updated. Useful for next/previous keyboard navigation (until Ctrl is released) or for incremental-search style document preview like Quick Open will eventually have.
var _traversingFileList = false;
EditorManager.activeEditorChange handler This event is triggered when an visible editor gains focus Therefore we need to Activate the pane that the active editor belongs to
function _activeEditorChange(e, current) {
if (current) {
var $container = current.$el.parent().parent(),
pane = _getPaneFromElement($container);
if (pane) {
// Editor is a full editor
if (pane.id !== _activePaneId) {
// we just need to set the active pane in this case
// it will dispatch the currentFileChange message as well
// as dispatching other events when the active pane changes
setActivePaneId(pane.id);
}
} else {
// Editor is an inline editor, find the parent pane
var parents = $container.parents(".view-pane");
if (parents.length === 1) {
$container = $(parents[0]);
pane = _getPaneFromElement($container);
if (pane) {
if (pane.id !== _activePaneId) {
// activate the pane which will put focus in the pane's doc
setActivePaneId(pane.id);
// reset the focus to the inline editor
current.focus();
}
}
}
}
}
}
Closes a file in the specified pane or panes
function _close(paneId, file, optionsIn) {
var options = optionsIn || {};
_forEachPaneOrPanes(paneId, function (pane) {
if (pane.removeView(file, options.noOpenNextFile) && (paneId === ACTIVE_PANE || pane.id === paneId)) {
_removeFileFromMRU(pane.id, file);
exports.trigger("workingSetRemove", file, false, pane.id);
return false;
}
});
}
Closes all files in the specified pane or panes
function _closeAll(paneId) {
_forEachPaneOrPanes(paneId, function (pane) {
var closedList = pane.getViewList();
closedList.forEach(function (file) {
_removeFileFromMRU(pane.id, file);
});
pane._reset();
exports.trigger("workingSetRemoveList", closedList, pane.id);
});
}
Closes a list of file in the specified pane or panes
function _closeList(paneId, fileList) {
_forEachPaneOrPanes(paneId, function (pane) {
var closedList = pane.removeViews(fileList);
closedList.forEach(function (file) {
_removeFileFromMRU(pane.id, file);
});
exports.trigger("workingSetRemoveList", closedList, pane.id);
});
}
Creates a pane for paneId if one doesn't already exist
function _createPaneIfNecessary(paneId) {
var newPane;
if (!_panes.hasOwnProperty(paneId)) {
newPane = new Pane(paneId, _$el);
_panes[paneId] = newPane;
exports.trigger("paneCreate", newPane.id);
newPane.$el.on("click.mainview dragover.mainview", function () {
setActivePaneId(newPane.id);
});
newPane.on("viewListChange.mainview", function () {
_updatePaneHeaders();
exports.trigger("workingSetUpdate", newPane.id);
});
newPane.on("currentViewChange.mainview", function (e, newView, oldView) {
_updatePaneHeaders();
if (_activePaneId === newPane.id) {
exports.trigger("currentFileChange",
newView && newView.getFile(),
newPane.id, oldView && oldView.getFile(),
newPane.id);
}
});
newPane.on("viewDestroy.mainView", function (e, view) {
_removeFileFromMRU(newPane.id, view.getFile());
});
}
return newPane;
}
Destroys an editor object if a document is no longer referenced
function _destroyEditorIfNotNeeded(document) {
if (!(document instanceof DocumentManager.Document)) {
throw new Error("_destroyEditorIfUnneeded() should be passed a Document");
}
if (document._masterEditor) {
// findPaneForDocument tries to locate the pane in which the document
// is either opened or will be opened (in the event that the document is
// in a working set but has yet to be opened) and then asks the pane
// to destroy the view if it doesn't need it anymore
var pane = _findPaneForDocument(document);
if (pane) {
// let the pane deceide if it wants to destroy the view if it's no needed
pane.destroyViewIfNotNeeded(document._masterEditor);
} else {
// in this case, the document isn't referenced at all so just destroy it
document._masterEditor.destroy();
}
}
}
Helper to abastract the common working set search functions
function _doFindInWorkingSet(paneId, fullPath, method) {
var result = -1;
_forEachPaneOrPanes(paneId, function (pane) {
var index = pane[method].call(pane, fullPath);
if (index >= 0) {
result = index;
return false;
}
});
return result;
}
Creates a split for the specified orientation
function _doSplit(orientation) {
var firstPane, newPane;
if (orientation === _orientation) {
return;
}
firstPane = _panes[FIRST_PANE];
Resizer.removeSizable(firstPane.$el);
if (_orientation) {
_$el.removeClass("split-" + _orientation.toLowerCase());
}
_$el.addClass("split-" + orientation.toLowerCase());
_orientation = orientation;
newPane = _createPaneIfNecessary(SECOND_PANE);
_makeFirstPaneResizable();
// reset the layout to 50/50 split
// if we changed orientation then
// the percentages are reset as well
_initialLayout();
exports.trigger("paneLayoutChange", _orientation);
// if new pane was created, and original pane is not empty, make new pane the active pane
if (newPane && getCurrentlyViewedFile(firstPane.id)) {
setActivePaneId(newPane.id);
}
}
Edits a document in the specified pane. This function is only used by:
function _edit(paneId, doc, optionsIn) {
var options = optionsIn || {};
var pane = _getPane(paneId);
// If file is untitled or otherwise not within project tree, add it to
// working set right now (don't wait for it to become dirty)
if (doc.isUntitled() || !ProjectManager.isWithinProject(doc.file.fullPath)) {
addToWorkingSet(paneId, doc.file);
}
// open document will show the editor if there is one already
EditorManager.openDocument(doc, pane, options);
_makeFileMostRecent(paneId, doc.file);
if (!options.noPaneActivate) {
setActivePaneId(paneId);
}
}
Locates the first MRU entry of a file for the requested pane
function _findFileInMRUList(paneId, file) {
return _.findIndex(_mruList, function (record) {
return (record.file.fullPath === file.fullPath && record.paneId === paneId);
});
}
Finds which pane a document belongs to
function _findPaneForDocument(document) {
// First check for an editor view of the document
var pane = _getPaneFromElement($(document._masterEditor.$el.parent().parent()));
if (!pane) {
// No view of the document, it may be in a working set and not yet opened
var info = findInAllWorkingSets(document.file.fullPath).shift();
if (info) {
pane = _panes[info.paneId];
}
}
return pane;
}
Iterates over the pane or ALL_PANES and calls the callback function for each.
function _forEachPaneOrPanes(paneId, callback) {
if (paneId === ALL_PANES) {
_.forEach(_panes, callback);
} else {
callback(_getPane(paneId));
}
}
Retrieves the Pane object for the given paneId
function _getPane(paneId) {
paneId = _resolvePaneId(paneId);
if (_panes[paneId]) {
return _panes[paneId];
}
return null;
}
Retrieves the Pane ID for the specified container
function _getPaneFromElement($el) {
return _.find(_panes, function (pane) {
if (pane.$el[0] === $el[0]) {
return pane;
}
});
}
function _getPaneIdForPath(fullPath) {
// Search all working sets and pull off the first one
var info = findInAllWorkingSets(fullPath).shift();
// Look for a view that has not been added to a working set
if (!info) {
_.forEach(_panes, function (pane) {
if (pane.getCurrentlyViewedPath() === fullPath) {
info = {paneId: pane.id};
return false;
}
});
}
if (!info) {
return null;
}
return info.paneId;
}
Sets up the initial layout so panes are evenly distributed This also sets css properties that aid in the layout when _updateLayout is called
function _initialLayout(forceRefresh) {
var panes = Object.keys(_panes),
size = 100 / panes.length;
_.forEach(_panes, function (pane) {
if (pane.id === FIRST_PANE) {
if (_orientation === VERTICAL) {
pane.$el.css({height: "100%",
width: size + "%",
float: "left"
});
} else {
pane.$el.css({ height: size + "%",
width: "100%"
});
}
} else {
if (_orientation === VERTICAL) {
pane.$el.css({ height: "100%",
width: "auto",
float: "none"
});
} else {
pane.$el.css({ width: "100%",
height: "50%"
});
}
}
_synchronizePaneSize(pane, forceRefresh);
});
}
Initializes the MainViewManager's view state
function _initialize($container) {
if (_activePaneId) {
throw new Error("MainViewManager has already been initialized");
}
_$el = $container;
_createPaneIfNecessary(FIRST_PANE);
_activePaneId = FIRST_PANE;
// One-time init so the pane has the "active" appearance
_panes[FIRST_PANE]._handleActivePaneChange(undefined, _activePaneId);
_initialLayout();
// This ensures that unit tests that use this function
// get an event handler for workspace events and we don't listen
// to the event before we've been initialized
WorkspaceManager.on("workspaceUpdateLayout", _updateLayout);
// Listen to key Alt-W to toggle between panes
CommandManager.register(Strings.CMD_SWITCH_PANE_FOCUS, Commands.CMD_SWITCH_PANE_FOCUS, switchPaneFocus);
KeyBindingManager.addBinding(Commands.CMD_SWITCH_PANE_FOCUS, {key: 'Alt-W'});
}
Determines if the pane id is a special pane id
function _isSpecialPaneId(paneId) {
return paneId === ACTIVE_PANE || paneId === ALL_PANES;
}
Loads the workingset state
function _loadViewState(e) {
// file root is appended for each project
var panes,
promises = [],
context = { location : { scope: "user",
layer: "project" } },
state = PreferencesManager.getViewState(PREFS_NAME, context);
function convertViewState() {
var context = { location : { scope: "user",
layer: "project" } },
files = PreferencesManager.getViewState(OLD_PREFS_NAME, context);
if (!files) {
// nothing to convert
return;
}
var result = {
orientation: null,
activePaneId: FIRST_PANE,
panes: {
"first-pane": []
}
};
// Add all files to the workingset without verifying that
// they still exist on disk (for faster project switching)
files.forEach(function (value) {
result.panes[FIRST_PANE].push(value);
});
return result;
}
if (!state) {
// not converted yet
state = convertViewState();
}
// reset
_mergePanes();
_mruList = [];
ViewStateManager.reset();
if (state) {
panes = Object.keys(state.panes);
_orientation = (panes.length > 1) ? state.orientation : null;
_.forEach(state.panes, function (paneState, paneId) {
_createPaneIfNecessary(paneId);
promises.push(_panes[paneId].loadState(paneState));
});
AsyncUtils.waitForAll(promises).then(function (opensList) {
// this will set the default layout of 50/50 or 100
// based on the number of panes
_initialLayout();
// More than 1 pane, then make it resizable
// and layout the panes from serialized state
if (panes.length > 1) {
_makeFirstPaneResizable();
// If the split state was serialized correctly
// then setup the splits according to was serialized
// Avoid a zero and negative split percentages
if ($.isNumeric(state.splitPercentage) && state.splitPercentage > 0) {
var prop;
if (_orientation === VERTICAL) {
prop = "width";
} else {
prop = "height";
}
_panes[FIRST_PANE].$el.css(prop, state.splitPercentage * 100 + "%");
_updateLayout();
}
}
if (_orientation) {
_$el.addClass("split-" + _orientation.toLowerCase());
exports.trigger("paneLayoutChange", _orientation);
}
_.forEach(_panes, function (pane) {
var fileList = pane.getViewList();
fileList.forEach(function (file) {
if (_findFileInMRUList(pane.id, file) !== -1) {
console.log(file.fullPath + " duplicated in mru list");
}
_mruList.push(_makeMRUListEntry(file, pane.id));
});
exports.trigger("workingSetAddList", fileList, pane.id);
});
promises = [];
opensList.forEach(function (openData) {
if (openData) {
promises.push(CommandManager.execute(Commands.FILE_OPEN, openData));
}
});
// finally set the active pane
AsyncUtils.waitForAll(promises).then(function () {
setActivePaneId(state.activePaneId);
});
});
}
}
Makes the file the most recent for the pane and the global mru lists
function _makeFileMostRecent(paneId, file) {
var index,
entry,
pane = _getPane(paneId);
if (!_traversingFileList) {
pane.makeViewMostRecent(file);
index = _findFileInMRUList(pane.id, file);
entry = _makeMRUListEntry(file, pane.id);
if (index !== -1) {
_mruList.splice(index, 1);
}
if (_findFileInMRUList(pane.id, file) !== -1) {
console.log(file.fullPath + " duplicated in mru list");
}
// add it to the front of the list
_mruList.unshift(entry);
}
}
Makes the first pane resizable
function _makeFirstPaneResizable() {
var firstPane = _panes[FIRST_PANE];
Resizer.makeResizable(firstPane.$el,
_orientation === HORIZONTAL ? Resizer.DIRECTION_VERTICAL : Resizer.DIRECTION_HORIZONTAL,
_orientation === HORIZONTAL ? Resizer.POSITION_BOTTOM : Resizer.POSITION_RIGHT,
MIN_PANE_SIZE, false, false, false, true, true);
firstPane.$el.on("panelResizeUpdate", function () {
_updateLayout();
});
}
Makes a MRU List Entry
function _makeMRUListEntry(file, paneId) {
return {file: file, paneId: paneId};
}
Makes the Pane's current file the most recent
function _makePaneMostRecent(paneId) {
var pane = _getPane(paneId);
if (pane.getCurrentlyViewedFile()) {
_makeFileMostRecent(paneId, pane.getCurrentlyViewedFile());
}
}
Merges second pane into first pane and opens the current file
function _mergePanes() {
if (_panes.hasOwnProperty(SECOND_PANE)) {
var firstPane = _panes[FIRST_PANE],
secondPane = _panes[SECOND_PANE],
fileList = secondPane.getViewList(),
lastViewed = getCurrentlyViewedFile();
Resizer.removeSizable(firstPane.$el);
firstPane.mergeFrom(secondPane);
exports.trigger("workingSetRemoveList", fileList, secondPane.id);
setActivePaneId(firstPane.id);
secondPane.$el.off(".mainview");
secondPane.off(".mainview");
secondPane.destroy();
delete _panes[SECOND_PANE];
exports.trigger("paneDestroy", secondPane.id);
exports.trigger("workingSetAddList", fileList, firstPane.id);
_mruList.forEach(function (record) {
if (record.paneId === secondPane.id) {
record.paneId = firstPane.id;
}
});
_$el.removeClass("split-" + _orientation.toLowerCase());
_orientation = null;
// this will set the remaining pane to 100%
_initialLayout();
exports.trigger("paneLayoutChange", _orientation);
// if the current view before the merger was in the pane
// that went away then reopen it so that it's now the current view again
if (lastViewed && getCurrentlyViewedFile() !== lastViewed) {
exports._open(firstPane.id, lastViewed);
}
}
}
moves a view from one pane to another
function _moveView(sourcePaneId, destinationPaneId, file, destinationIndex) {
var result = new $.Deferred(),
sourcePane = _getPane(sourcePaneId),
destinationPane = _getPane(destinationPaneId);
sourcePane.moveView(file, destinationPane, destinationIndex)
.done(function () {
// remove existing entry from mrulist for the same document if present
_removeFileFromMRU(destinationPane.id, file);
// update the mru list
_mruList.every(function (record) {
if (record.file === file && record.paneId === sourcePane.id) {
record.paneId = destinationPane.id;
return false;
}
return true;
});
exports.trigger("workingSetMove", file, sourcePane.id, destinationPane.id);
result.resolve();
});
return result.promise();
}
moves a working set item from one index to another shifting the items after in the working set up and reinserting it at the desired location
function _moveWorkingSetItem(paneId, fromIndex, toIndex) {
var pane = _getPane(paneId);
pane.moveWorkingSetItem(fromIndex, toIndex);
exports.trigger("workingSetSort", pane.id);
exports.trigger("_workingSetDisableAutoSort", pane.id);
}
Opens a file in the specified pane this can be used to open a file with a custom viewer or a document for editing. If it's a document for editing, edit is called on the document
function _open(paneId, file, optionsIn) {
var result = new $.Deferred(),
options = optionsIn || {};
function doPostOpenActivation() {
if (!options.noPaneActivate) {
setActivePaneId(paneId);
}
}
if (!file || !_getPane(paneId)) {
return result.reject("bad argument").promise();
}
// See if there is already a view for the file
var pane = _getPane(paneId);
// See if there is a factory to create a view for this file
// we want to do this first because, we don't want our internal
// editor to edit files for which there are suitable viewfactories
var factory = MainViewFactory.findSuitableFactoryForPath(file.fullPath);
if (factory) {
file.exists(function (fileError, fileExists) {
if (fileExists) {
// let the factory open the file and create a view for it
factory.openFile(file, pane)
.done(function () {
// if we opened a file that isn't in the project
// then add the file to the working set
if (!ProjectManager.isWithinProject(file.fullPath)) {
addToWorkingSet(paneId, file);
}
doPostOpenActivation();
result.resolve(file);
})
.fail(function (fileError) {
result.reject(fileError);
});
} else {
result.reject(fileError || FileSystemError.NOT_FOUND);
}
});
} else {
DocumentManager.getDocumentForPath(file.fullPath, file)
.done(function (doc) {
if (doc) {
_edit(paneId, doc, $.extend({}, options, {
noPaneActivate: true
}));
doPostOpenActivation();
result.resolve(doc.file);
} else {
result.resolve(null);
}
})
.fail(function (fileError) {
result.reject(fileError);
});
}
result.done(function () {
_makeFileMostRecent(paneId, file);
});
return result;
}
DocumentManager.pathDeleted Event handler to remove a file from the MRU list
function _removeDeletedFileFromMRU(e, fullPath) {
var index,
compare = function (record) {
return (record.file.fullPath === fullPath);
};
// find and remove all instances
do {
index = _.findIndex(_mruList, compare);
if (index !== -1) {
_mruList.splice(index, 1);
}
} while (index !== -1);
}
Removes a file from the global MRU list. Future versions of this implementation may support the ALL_PANES constant but FOCUS_PANE is not allowed
function _removeFileFromMRU(paneId, file) {
var index,
compare = function (record) {
return (record.file === file && record.paneId === paneId);
};
// find and remove all instances
do {
index = _.findIndex(_mruList, compare);
if (index !== -1) {
_mruList.splice(index, 1);
}
} while (index !== -1);
}
Removes a file the specified pane
function _removeView(paneId, file, suppressRedraw) {
var pane = _getPane(paneId);
if (pane.removeView(file)) {
_removeFileFromMRU(pane.id, file);
exports.trigger("workingSetRemove", file, suppressRedraw, pane.id);
}
}
Resolve paneId to actual pane.
function _resolvePaneId(paneId) {
if (!paneId || paneId === ACTIVE_PANE) {
return getActivePaneId();
}
return paneId;
}
Saves the workingset state
function _saveViewState() {
function _computeSplitPercentage() {
var available,
used;
if (getPaneCount() === 1) {
// just short-circuit here and
// return 100% to avoid any rounding issues
return 1;
} else {
if (_orientation === VERTICAL) {
available = _$el.innerWidth();
used = _panes[FIRST_PANE].$el.width();
} else {
available = _$el.innerHeight();
used = _panes[FIRST_PANE].$el.height();
}
return used / available;
}
}
var projectRoot = ProjectManager.getProjectRoot(),
context = { location : { scope: "user",
layer: "project",
layerID: projectRoot.fullPath } },
state = {
orientation: _orientation,
activePaneId: getActivePaneId(),
splitPercentage: _computeSplitPercentage(),
panes: {
}
};
if (!projectRoot) {
return;
}
_.forEach(_panes, function (pane) {
state.panes[pane.id] = pane.saveState();
});
PreferencesManager.setViewState(PREFS_NAME, state, context);
}
sorts the pane's view list
function _sortWorkingSet(paneId, compareFn) {
_forEachPaneOrPanes(paneId, function (pane) {
pane.sortViewList(compareFn);
exports.trigger("workingSetSort", pane.id);
});
}
Mutually exchanges the files at the indexes passed by parameters.
function _swapWorkingSetListIndexes(paneId, index1, index2) {
var pane = _getPane(paneId);
pane.swapViewListIndexes(index1, index2);
exports.trigger("workingSetSort", pane.id);
exports.trigger("_workingSetDisableAutoSort", pane.id);
}
Synchronizes the pane's sizer element, updates the pane's resizer maxsize value and tells the pane to update its layout
function _synchronizePaneSize(pane, forceRefresh) {
var available;
if (_orientation === VERTICAL) {
available = _$el.innerWidth();
} else {
available = _$el.innerHeight();
}
// Update the pane's sizer element if it has one and update the max size
Resizer.resyncSizer(pane.$el);
pane.$el.data("maxsize", available - MIN_PANE_SIZE);
pane.updateLayout(forceRefresh);
}
Event handler for "workspaceUpdateLayout" to update the layout
function _updateLayout(event, viewAreaHeight, forceRefresh) {
var available;
if (_orientation === VERTICAL) {
available = _$el.innerWidth();
} else {
available = _$el.innerHeight();
}
_.forEach(_panes, function (pane) {
// For VERTICAL orientation, we set the second pane to be width: auto
// so that it resizes to fill the available space in the containing div
// unfortunately, that doesn't work in the HORIZONTAL orientation so we
// must update the height and convert it into a percentage
if (pane.id === SECOND_PANE && _orientation === HORIZONTAL) {
var percentage = ((_panes[FIRST_PANE].$el.height() + 1) / available);
pane.$el.css("height", 100 - (percentage * 100) + "%");
}
_synchronizePaneSize(pane, forceRefresh);
});
}
Updates the header text for all panes
function _updatePaneHeaders() {
_forEachPaneOrPanes(ALL_PANES, function (pane) {
pane.updateHeaderText();
});
}
Adds the given file list to the end of the workingset.
function addListToWorkingSet(paneId, fileList) {
var uniqueFileList,
pane = _getPane(paneId);
uniqueFileList = pane.addListToViewList(fileList);
uniqueFileList.forEach(function (file) {
if (_findFileInMRUList(pane.id, file) !== -1) {
console.log(file.fullPath + " duplicated in mru list");
}
_mruList.push(_makeMRUListEntry(file, pane.id));
});
exports.trigger("workingSetAddList", uniqueFileList, pane.id);
// find all of the files that could be added but were not
var unsolvedList = fileList.filter(function (item) {
// if the file open in another pane, then add it to the list of unsolvedList
return (pane.findInViewList(item.fullPath) === -1 && _getPaneIdForPath(item.fullPath));
});
// Use the pane id of the first one in the list for pane id and recurse
// if we add more panes, then this will recurse until all items in the list are satisified
if (unsolvedList.length) {
addListToWorkingSet(_getPaneIdForPath(unsolvedList[0].fullPath), unsolvedList);
}
}
Adds the given file to the end of the workingset, if it is not already there. This API does not create a view of the file, it just adds it to the working set Views of files in the working set are persisted and are not destroyed until the user closes the file using FILE_CLOSE; Views are created using FILE_OPEN and, when opened, are made the current view. If a File is already opened then the file is just made current and its view is shown.
function addToWorkingSet(paneId, file, index, force) {
// look for the file to have already been added to another pane
var pane = _getPane(paneId);
if (!pane) {
throw new Error("invalid pane id: " + paneId);
}
var result = pane.reorderItem(file, index, force),
entry = _makeMRUListEntry(file, pane.id);
// handles the case of save as so that the file remains in the
// the same location in the working set as the file that was renamed
if (result === pane.ITEM_FOUND_NEEDS_SORT) {
console.warn("pane.reorderItem returned pane.ITEM_FOUND_NEEDS_SORT which shouldn't happen " + file);
exports.trigger("workingSetSort", pane.id);
} else if (result === pane.ITEM_NOT_FOUND) {
index = pane.addToViewList(file, index);
if (_findFileInMRUList(pane.id, file) === -1) {
// Add to or update the position in MRU
if (pane.getCurrentlyViewedFile() === file) {
_mruList.unshift(entry);
} else {
_mruList.push(entry);
}
}
exports.trigger("workingSetAdd", file, index, pane.id);
}
}
Indicates that traversal has begun. Can be called any number of times.
function beginTraversal() {
_traversingFileList = true;
}
Caches the specified pane's current scroll state If there was already cached state for the specified pane, it is discarded and overwritten
function cacheScrollState(paneId) {
_forEachPaneOrPanes(paneId, function (pane) {
_paneScrollStates[pane.id] = pane.getScrollState();
});
}
Un-freezes the MRU list after one or more beginTraversal() calls. Whatever file is current is bumped to the front of the MRU list.
function endTraversal() {
var pane = _getPane(ACTIVE_PANE);
if (_traversingFileList) {
_traversingFileList = false;
_makeFileMostRecent(pane.id, pane.getCurrentlyViewedFile());
}
}
Finds all instances of the specified file in all working sets. If there is a temporary view of the file, it is not part of the result set
function findInAllWorkingSets(fullPath) {
var index,
result = [];
_.forEach(_panes, function (pane) {
index = pane.findInViewList(fullPath);
if (index >= 0) {
result.push({paneId: pane.id, index: index});
}
});
return result;
}
Gets the index of the file matching fullPath in the workingset
function findInWorkingSet(paneId, fullPath) {
return _doFindInWorkingSet(paneId, fullPath, "findInViewList");
}
Gets the index of the file matching fullPath in the added order workingset
function findInWorkingSetByAddedOrder(paneId, fullPath) {
return _doFindInWorkingSet(paneId, fullPath, "findInViewListAddedOrder");
}
Gets the index of the file matching fullPath in the MRU order workingset
function findInWorkingSetByMRUOrder(paneId, fullPath) {
return _doFindInWorkingSet(paneId, fullPath, "findInViewListMRUOrder");
}
Focuses the current pane. If the current pane has a current view, then the pane will focus the view.
function focusActivePane() {
_getPane(ACTIVE_PANE).focus();
}
Retrieves the currently active Pane Id
function getActivePaneId() {
return _activePaneId;
}
Retrieves the list of all open files including temporary views
function getAllOpenFiles() {
var result = getWorkingSet(ALL_PANES);
_.forEach(_panes, function (pane) {
var file = pane.getCurrentlyViewedFile();
if (file) {
result = _.union(result, [file]);
}
});
return result;
}
Retrieves the currently viewed file of the specified paneId
function getCurrentlyViewedFile(paneId) {
var pane = _getPane(paneId);
return pane ? pane.getCurrentlyViewedFile() : null;
}
Retrieves the currently viewed path of the pane specified by paneId
function getCurrentlyViewedPath(paneId) {
var file = getCurrentlyViewedFile(paneId);
return file ? file.fullPath : null;
}
Retrieves the current layout scheme
function getLayoutScheme() {
var result = {
rows: 1,
columns: 1
};
if (_orientation === HORIZONTAL) {
result.rows = 2;
} else if (_orientation === VERTICAL) {
result.columns = 2;
}
return result;
}
Retrieves the number of panes
function getPaneCount() {
return Object.keys(_panes).length;
}
Retrieves the list of all open pane ids
function getPaneIdList() {
return Object.keys(_panes);
}
Retrieves the title to display in the workingset view
function getPaneTitle(paneId) {
return _paneTitles[paneId][_orientation];
}
Retrieves the WorkingSet for the given paneId not including temporary views
function getWorkingSet(paneId) {
var result = [];
_forEachPaneOrPanes(paneId, function (pane) {
var viewList = pane.getViewList();
result = _.union(result, viewList);
});
return result;
}
Retrieves the size of the selected pane's view list
function getWorkingSetSize(paneId) {
var result = 0;
_forEachPaneOrPanes(paneId, function (pane) {
result += pane.getViewListSize();
});
return result;
}
Checks whether a file is listed exclusively in the provided pane
function isExclusiveToPane(file, paneId) {
paneId = paneId === ACTIVE_PANE && _activePaneId ? _activePaneId : paneId;
var index = _.findIndex(_mruList, function (record) {
return (record.file.fullPath === file.fullPath && record.paneId !== paneId);
});
return index === -1;
}
Restores the scroll state from cache and applies the heightDelta The view implementation is responsible for applying or ignoring the heightDelta. This is used primarily when a modal bar opens to keep the editor from scrolling the current page out of view in order to maintain the appearance. The state is removed from the cache after calling this function.
function restoreAdjustedScrollState(paneId, heightDelta) {
_forEachPaneOrPanes(paneId, function (pane) {
pane.restoreAndAdjustScrollState(_paneScrollStates[pane.id], heightDelta);
delete _paneScrollStates[pane.id];
});
}
Switch active pane to the specified pane id (or ACTIVE_PANE/ALL_PANES, in which case this call does nothing).
function setActivePaneId(newPaneId) {
if (!_isSpecialPaneId(newPaneId) && newPaneId !== _activePaneId) {
var oldPaneId = _activePaneId,
oldPane = _getPane(ACTIVE_PANE),
newPane = _getPane(newPaneId);
if (!newPane) {
throw new Error("invalid pane id: " + newPaneId);
}
_activePaneId = newPaneId;
exports.trigger("activePaneChange", newPaneId, oldPaneId);
exports.trigger("currentFileChange", _getPane(ACTIVE_PANE).getCurrentlyViewedFile(),
newPaneId,
oldPane.getCurrentlyViewedFile(),
oldPaneId);
_makePaneMostRecent(_activePaneId);
focusActivePane();
}
}
Changes the layout scheme
function setLayoutScheme(rows, columns) {
if ((rows < 1) || (rows > 2) || (columns < 1) || (columns > 2) || (columns === 2 && rows === 2)) {
console.error("setLayoutScheme unsupported layout " + rows + ", " + columns);
return false;
}
if (rows === columns) {
_mergePanes();
} else if (rows > columns) {
_doSplit(HORIZONTAL);
} else {
_doSplit(VERTICAL);
}
return true;
}
Switch between panes
function switchPaneFocus() {
var $firstPane = $('#first-pane'), $secondPane = $('#second-pane');
if($firstPane.hasClass('active-pane')) {
$secondPane.click();
}
else {
$firstPane.click();
}
}
Get the next or previous file in the MRU list.
function traverseToNextViewByMRU(direction) {
var file = getCurrentlyViewedFile(),
paneId = getActivePaneId(),
index = _.findIndex(_mruList, function (record) {
return (record.file === file && record.paneId === paneId);
});
return ViewUtils.traverseViewArray(_mruList, index, direction);
}
Get the next or previous file in list order.
function traverseToNextViewInListOrder(direction) {
var file = getCurrentlyViewedFile(),
curPaneId = getActivePaneId(),
allFiles = [],
index;
getPaneIdList().forEach(function (paneId) {
var paneFiles = getWorkingSet(paneId).map(function (file) {
return { file: file, pane: paneId };
});
allFiles = allFiles.concat(paneFiles);
});
index = _.findIndex(allFiles, function (record) {
return (record.file === file && record.pane === curPaneId);
});
return ViewUtils.traverseViewArray(allFiles, index, direction);
}