Modules (181)

MainViewManager

Description

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:

  • activePaneChange - When the active pane changes. There will always be an active pane.
    (e, newPaneId:string, oldPaneId:string)
    
  • currentFileChange -- When the user has switched to another pane, file, document. When the user closes a view and there are no other views to show the current file will be null.
    (e, newFile:File, newPaneId:string, oldFile:File, oldPaneId:string)
    
  • paneLayoutChange -- When Orientation changes.
    (e, orientation:string)
    
  • paneCreate -- When a pane is created
    (e, paneId:string)
    
  • paneDestroy -- When a pane is destroyed
    (e, paneId:string)
    

To listen for working set changes, you must listen to all of these events:

  • workingSetAdd -- When a file is added to the working set
    (e, fileAdded:File, index:number, paneId:string)
    
  • workingSetAddList -- When multiple files are added to the working set
    (e, fileAdded:Array.<File>, paneId:string)
    
  • workingSetMove - When a File has moved to a different working set
    (e, File:FILE, sourcePaneId:string, destinationPaneId:string)
    
  • workingSetRemove -- When a file is removed from the working set
    (e, fileRemoved:File, suppressRedraw:boolean, paneId:string)
    
  • workingSetRemoveList -- When multiple files are removed from the working set
    (e, filesRemoved:Array.<File>, paneId:string)
    
  • workingSetSort -- When a pane's view array is reordered without additions or removals.
    (e, paneId:string)
    
  • workingSetUpdate -- When changes happen due to system events such as a file being deleted.
                        listeners should discard all working set info and rebuilt it from the pane
                        by calling getWorkingSet()
    (e, paneId:string)
    
  • _workingSetDisableAutoSort -- When the working set is reordered by manually dragging a file.
    (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);

Dependencies

Variables

Public API

ACTIVE_PANE Constant

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";
Public API

ALL_PANES Constant

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";
Private Public API

FIRST_PANE Constant

Internal pane id

Private
    var FIRST_PANE          = "first-pane";
Private

HORIZONTAL Constant

Horizontal layout state name

Private
    var HORIZONTAL          = "HORIZONTAL";
Private

MIN_PANE_SIZE Constant

The minimum width or height that a pane can be

Private
    var MIN_PANE_SIZE      = 75;
Private

OLD_PREFS_NAME Constant

Legacy Preference setting name used to migrate old preferences

Private
    var OLD_PREFS_NAME      = "project.files";
Private

PREFS_NAME Constant

Preference setting name for the MainView Saved State

Private
    var PREFS_NAME          = "mainView.state";
Private Public API

SECOND_PANE Constant

Internal pane id

Private
    var SECOND_PANE         = "second-pane";
Private

VERTICAL Constant

Vertical layout state name

Private
    var VERTICAL            = "VERTICAL";
Private

_$el

DOM element hosting the Main View.

Type
jQuery
Private
    var _$el;
Private

_activePaneId

current pane id. May not be null

Type
!string
Private
    var _activePaneId = null;
Private

_mruList

The global MRU list (for traversing)

Type
Array.<file:File, paneId:string>
Private
    var _mruList = [];
Private

_orientation

current orientation (null, VERTICAL or HORIZONTAL)

Type
string=
Private
    var _orientation = null;
Private

_paneScrollStates

map of pane scroll states

Type
Object.map<string, *>
Private
    var _paneScrollStates = {};
Private

_paneTitles

localized pane titles

Type
Object.<FIRST_PANE, SECOND_PANE, <VERTICAL.string, HORIZONTAL.string>}
See
{@link #getPaneTitle} for more information
Private
    var _paneTitles  = {};
Private

_panes

Maps paneId to Pane objects

Type
Object.<string, Pane>
Private
    var _panes = {};
Private

_traversingFileList

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.

Type
!boolean
Private
    var _traversingFileList = false;

Functions

Private

_activeEditorChange

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

e non-nullable jQuery.Event
- jQuery Event object
current optional Editor
- editor being made the current editor
    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();
                        }
                    }
                }
            }
        }
    }
Private Public API

_close

Closes a file in the specified pane or panes

paneId non-nullable string
- id of the pane in which to open the document
file non-nullable File
- file to close
optionsIn Object={noOpenNextFile:boolean}
- options This function does not fail if the file is not open
    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;
            }
        });
    }
Private Public API

_closeAll

Closes all files in the specified pane or panes

paneId non-nullable string
- id of the pane in which to open the document This function does not fail if the file is not open
    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);
        });
    }
Private Public API

_closeList

Closes a list of file in the specified pane or panes

paneId non-nullable string
- id of the pane in which to open the document
fileList non-nullable Array.<File>
- files to close This function does not fail if the file is not open
    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);
        });
    }
Private

_createPaneIfNecessary

Creates a pane for paneId if one doesn't already exist

paneId non-nullable string
- id of the pane to create
Returns: ?Pane
- the pane object of the new pane, or undefined if no pane created
    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;
    }
Private Public API

_destroyEditorIfNotNeeded

Destroys an editor object if a document is no longer referenced

doc non-nullable Document
- document to destroy
    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();
            }
        }
    }
Private

_doFindInWorkingSet

Helper to abastract the common working set search functions

paneId non-nullable string
- id of the pane to search or ALL_PANES to search all panes
fullPath non-nullable string
- path of the file to locate
method non-nullable string
- name of the method to use for searching "findInViewList", "findInViewListAddedOrder" or "FindInViewListMRUOrder"
    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;
    }
Private

_doSplit

Creates a split for the specified orientation

orientation non-nullable string
(VERTICAL|HORIZONTAL)
    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);
        }
    }
Private Public API

_edit

Edits a document in the specified pane. This function is only used by:

  • Unit Tests (which construct Mock Document objects),
  • by File > New because there is yet to be an established File object
  • by Find In Files which needs to open documents synchronously in some cases Do not use this API it is for internal use only
paneId non-nullable string
- id of the pane in which to open the document
doc non-nullable Document
- document to edit
optionsIn optional {noPaneActivate:boolean=}
- options
    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);
        }
    }
Private

_findFileInMRUList

Locates the first MRU entry of a file for the requested pane

paneId non-nullable string
- the paneId
File non-nullable File
- the file
Returns: {file:File,paneId:string}
    function _findFileInMRUList(paneId, file) {
        return _.findIndex(_mruList, function (record) {
            return (record.file.fullPath === file.fullPath && record.paneId === paneId);
        });
    }
Private

_findPaneForDocument

Finds which pane a document belongs to

document non-nullable Document
- the document to locate
Returns: ?Pane
the pane where the document lives or NULL if it isn't in a pane
    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;
    }
Private

_forEachPaneOrPanes

Iterates over the pane or ALL_PANES and calls the callback function for each.

paneId non-nullable string
- id of the pane in which to adjust the scroll state, ALL_PANES or ACTIVE_PANE
callback non-nullable function(!pane:Pane):boolean
- function to callback on to perform work. The callback will receive a Pane and should return false to stop iterating.
    function _forEachPaneOrPanes(paneId, callback) {
        if (paneId === ALL_PANES) {
            _.forEach(_panes, callback);
        } else {
            callback(_getPane(paneId));
        }
    }
Private Public API

_getPane

Retrieves the Pane object for the given paneId

paneId non-nullable string
- id of the pane to retrieve
Returns: ?Pane
the Pane object or null if a pane object doesn't exist for the pane
    function _getPane(paneId) {
        paneId = _resolvePaneId(paneId);

        if (_panes[paneId]) {
            return _panes[paneId];
        }

        return null;
    }
Private

_getPaneFromElement

Retrieves the Pane ID for the specified container

$el non-nullable jQuery
- the element of the pane to fetch
Returns: ?string
the id of the pane that matches the container or undefined if a pane doesn't exist for that container
    function _getPaneFromElement($el) {
        return _.find(_panes, function (pane) {
            if (pane.$el[0] === $el[0]) {
                return pane;
            }
        });
    }
Private Public API

_getPaneIdForPath

fullPath non-nullable string
- full path of the file to search for
Returns: ?string
pane id where the file has been opened or null if it wasn't found
    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;
    }
Private

_initialLayout

Sets up the initial layout so panes are evenly distributed This also sets css properties that aid in the layout when _updateLayout is called

forceRefresh boolean
- true to force a resize and refresh of the entire view
    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);
        });
    }
Private Public API

_initialize

Initializes the MainViewManager's view state

$container jQuery
- the container where the main view will live
    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'});
    }
Private

_isSpecialPaneId

Determines if the pane id is a special pane id

paneId non-nullable string
- the id to test
Returns: boolean
true if the pane id is a special identifier, false if not
    function _isSpecialPaneId(paneId) {
        return paneId === ACTIVE_PANE || paneId === ALL_PANES;
    }
Private

_loadViewState

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);
                });
            });
        }
    }
Private

_makeFileMostRecent

Makes the file the most recent for the pane and the global mru lists

paneId non-nullable string
- id of the pane to mae th file most recent or ACTIVE_PANE
file non-nullable File
- File object to make most recent
    function _makeFileMostRecent(paneId, file) {
        var index,
            entry,
            pane = _getPane(paneId);

        if (!_traversingFileList) {
            pane.makeViewMostRecent(file);

            index = _.findIndex(_mruList, function (record) {
                return (record.file === file && record.paneId === pane.id);
            });

            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);
        }
    }
Private

_makeFirstPaneResizable

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();
        });
    }
Private

_makeMRUListEntry

Makes a MRU List Entry

File non-nullable File
- the file
paneId non-nullable string
- the paneId
Returns: {file:File,paneId:string}
    function _makeMRUListEntry(file, paneId) {
        return {file: file, paneId: paneId};
    }
Private

_makePaneMostRecent

Makes the Pane's current file the most recent

paneId non-nullable string
- id of the pane to make the file most recent, or ACTIVE_PANE
file non-nullable File
- File object to make most recent
    function _makePaneMostRecent(paneId) {
        var pane = _getPane(paneId);

        if (pane.getCurrentlyViewedFile()) {
            _makeFileMostRecent(paneId, pane.getCurrentlyViewedFile());
        }
    }
Private

_mergePanes

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);
            }
        }
    }
Private Public API

_moveView

moves a view from one pane to another

sourcePaneId non-nullable string
- id of the source pane
destinationPaneId non-nullable string
- id of the destination pane
file non-nullable File
- the File to move
destinationIndex Number
- the working set index of the file in the destination pane
Returns: jQuery.Promise
a promise that resolves when the move has completed.
    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();
    }
Private Public API

_moveWorkingSetItem

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

paneId non-nullable string
- id of the pane to sort
fromIndex non-nullable number
- the index of the item to move
toIndex non-nullable number
- the index to move to
    function _moveWorkingSetItem(paneId, fromIndex, toIndex) {
        var pane = _getPane(paneId);

        pane.moveWorkingSetItem(fromIndex, toIndex);
        exports.trigger("workingSetSort", pane.id);
        exports.trigger("_workingSetDisableAutoSort", pane.id);
    }
Private Public API

_open

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

paneId non-nullable string
- id of the pane in which to open the document
file non-nullable File
- file to open
optionsIn optional {noPaneActivate:boolean=}
- options
Returns: jQuery.Promise
promise that resolves to a File object or rejects with a File error or string
    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)
                .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;
    }
Private

_removeDeletedFileFromMRU

DocumentManager.pathDeleted Event handler to remove a file from the MRU list

e non-nullable jQuery.event
-
fullPath non-nullable string
- path of the file to remove
    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);
    }
Private

_removeFileFromMRU

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

paneId non-nullable string
- Must be a valid paneId (not a shortcut e.g. ALL_PANES) @ @param {File} file The file object to remove.
    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);
    }
Private Public API

_removeView

Removes a file the specified pane

paneId non-nullable string
- Must be a valid paneId (not a shortcut e.g. ALL_PANES)
file non-nullable File
- the File to remove
suppressRedraw optional boolean
- true to tell listeners not to redraw Use the suppressRedraw flag when calling this function along with many changes to prevent flicker
    function _removeView(paneId, file, suppressRedraw) {
        var pane = _getPane(paneId);

        if (pane.removeView(file)) {
            _removeFileFromMRU(pane.id, file);
            exports.trigger("workingSetRemove", file, suppressRedraw, pane.id);
        }
    }
Private

_resolvePaneId

Resolve paneId to actual pane.

paneId nullable string
- id of the desired pane. May be symbolic or null (to indicate current pane)
Returns: string
id of the pane in which to open the document
    function _resolvePaneId(paneId) {
        if (!paneId || paneId === ACTIVE_PANE) {
            return getActivePaneId();
        }
        return paneId;
    }
Private

_saveViewState

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);
    }
Private Public API

_sortWorkingSet

sorts the pane's view list

paneId non-nullable string
- id of the pane to sort, ALL_PANES or ACTIVE_PANE
compareFn sortFunctionCallback
- callback to determine sort order (called on each item)
    function _sortWorkingSet(paneId, compareFn) {
        _forEachPaneOrPanes(paneId, function (pane) {
            pane.sortViewList(compareFn);
            exports.trigger("workingSetSort", pane.id);
        });
    }
Private Public API

_swapWorkingSetListIndexes

Mutually exchanges the files at the indexes passed by parameters.

paneId non-nullable string
- id of the pane to swap indices or ACTIVE_PANE
index1 non-nullable number
- the index on the left
index2 non-nullable number
- the index on the rigth
    function _swapWorkingSetListIndexes(paneId, index1, index2) {
        var pane = _getPane(paneId);

        pane.swapViewListIndexes(index1, index2);
        exports.trigger("workingSetSort", pane.id);
        exports.trigger("_workingSetDisableAutoSort", pane.id);
    }
Private

_synchronizePaneSize

Synchronizes the pane's sizer element, updates the pane's resizer maxsize value and tells the pane to update its layout

forceRefresh boolean
- true to force a resize and refresh of the entire view
    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);
    }
Private

_updateLayout

Event handler for "workspaceUpdateLayout" to update the layout

event jQuery.Event
- jQuery event object
viewAreaHeight number
- unused
forceRefresh boolean
- true to force a resize and refresh of the entire view
    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);
        });
    }
Private

_updatePaneHeaders

Updates the header text for all panes

    function _updatePaneHeaders() {
        _forEachPaneOrPanes(ALL_PANES, function (pane) {
            pane.updateHeaderText();
        });

    }
Public API

addListToWorkingSet

Adds the given file list to the end of the workingset.

paneId non-nullable string
- The id of the pane in which to add the file object to or ACTIVE_PANE
fileList non-nullable Array.<File>
- Array of files to add to the pane
    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);
        }
    }
Public API

addToWorkingSet

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.

paneId non-nullable string
- The id of the pane in which to add the file object to or ACTIVE_PANE
file non-nullable File
- The File object to add to the workingset
index optional number
- Position to add to list (defaults to last); -1 is ignored
forceRedraw optional boolean
- If true, a workingset change notification is always sent (useful if suppressRedraw was used with removeView() earlier)
    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);
        }
    }
Public API

beginTraversal

Indicates that traversal has begun. Can be called any number of times.

    function beginTraversal() {
        _traversingFileList = true;
    }
Public API

cacheScrollState

Caches the specified pane's current scroll state If there was already cached state for the specified pane, it is discarded and overwritten

paneId non-nullable string
- id of the pane in which to cache the scroll state, ALL_PANES or ACTIVE_PANE
    function cacheScrollState(paneId) {
        _forEachPaneOrPanes(paneId, function (pane) {
            _paneScrollStates[pane.id] = pane.getScrollState();
        });
    }
Public API

endTraversal

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());
        }
    }
Public API

findInAllWorkingSets

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

fullPath non-nullable string
- path of the file to find views of
Returns: Array.<{pane:string,index:number}>
an array of paneId/index records
    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;
    }
Public API

findInWorkingSet

Gets the index of the file matching fullPath in the workingset

paneId non-nullable string
- id of the pane in which to search or ALL_PANES or ACTIVE_PANE
fullPath non-nullable string
- full path of the file to search for
Returns: number
index, -1 if not found.
    function findInWorkingSet(paneId, fullPath) {
        return _doFindInWorkingSet(paneId, fullPath, "findInViewList");
    }
Public API

findInWorkingSetByAddedOrder

Gets the index of the file matching fullPath in the added order workingset

paneId non-nullable string
- id of the pane in which to search or ALL_PANES or ACTIVE_PANE
fullPath non-nullable string
- full path of the file to search for
Returns: number
index, -1 if not found.
    function findInWorkingSetByAddedOrder(paneId, fullPath) {
        return _doFindInWorkingSet(paneId, fullPath, "findInViewListAddedOrder");
    }
Public API

findInWorkingSetByMRUOrder

Gets the index of the file matching fullPath in the MRU order workingset

paneId non-nullable string
- id of the pane in which to search or ALL_PANES or ACTIVE_PANE
fullPath non-nullable string
- full path of the file to search for
Returns: number
index, -1 if not found.
    function findInWorkingSetByMRUOrder(paneId, fullPath) {
        return _doFindInWorkingSet(paneId, fullPath, "findInViewListMRUOrder");
    }
Public API

focusActivePane

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();
    }
Public API

getActivePaneId

Retrieves the currently active Pane Id

Returns: !string
Active Pane's ID.
    function getActivePaneId() {
        return _activePaneId;
    }
Public API

getAllOpenFiles

Retrieves the list of all open files including temporary views

Returns: array.<File>
the list of all open files in all open panes
    function getAllOpenFiles() {
        var result = getWorkingSet(ALL_PANES);
        _.forEach(_panes, function (pane) {
            var file = pane.getCurrentlyViewedFile();
            if (file) {
                result = _.union(result, [file]);
            }
        });
        return result;
    }
Public API

getCurrentlyViewedFile

Retrieves the currently viewed file of the specified paneId

paneId nullable string
- the id of the pane in which to retrieve the currently viewed file
Returns: ?File
File object of the currently viewed file, or null if there isn't one or there's no such pane
    function getCurrentlyViewedFile(paneId) {
        var pane = _getPane(paneId);
        return pane ? pane.getCurrentlyViewedFile() : null;
    }
Public API

getCurrentlyViewedPath

Retrieves the currently viewed path of the pane specified by paneId

paneId nullable string
- the id of the pane in which to retrieve the currently viewed path
Returns: ?string
the path of the currently viewed file or null if there isn't one
    function getCurrentlyViewedPath(paneId) {
        var file = getCurrentlyViewedFile(paneId);
        return file ? file.fullPath : null;
    }
Public API

getLayoutScheme

Retrieves the current layout scheme

Returns: !{rows: number,columns: number>}
    function getLayoutScheme() {
        var result = {
            rows: 1,
            columns: 1
        };

        if (_orientation === HORIZONTAL) {
            result.rows = 2;
        } else if (_orientation === VERTICAL) {
            result.columns = 2;
        }

        return result;
    }
Public API

getPaneCount

Retrieves the number of panes

Returns: number
    function getPaneCount() {
        return Object.keys(_panes).length;
    }
Public API

getPaneIdList

Retrieves the list of all open pane ids

Returns: array.<string>
the list of all open panes
    function getPaneIdList() {
        return Object.keys(_panes);
    }
Public API

getPaneTitle

Retrieves the title to display in the workingset view

paneId non-nullable string
- id of the pane in which to get the title
Returns: ?string
title
    function getPaneTitle(paneId) {
        return _paneTitles[paneId][_orientation];
    }
Public API

getWorkingSet

Retrieves the WorkingSet for the given paneId not including temporary views

paneId non-nullable string
- id of the pane in which to get the view list, ALL_PANES or ACTIVE_PANE
Returns: Array.<File>
    function getWorkingSet(paneId) {
        var result = [];

        _forEachPaneOrPanes(paneId, function (pane) {
            var viewList = pane.getViewList();
            result = _.union(result, viewList);
        });

        return result;
    }
Public API

getWorkingSetSize

Retrieves the size of the selected pane's view list

paneId non-nullable string
- id of the pane in which to get the workingset size. Can use `ALL_PANES` or `ACTIVE_PANE`
Returns: !number
the number of items in the specified pane
    function getWorkingSetSize(paneId) {
        var result = 0;
        _forEachPaneOrPanes(paneId, function (pane) {
            result += pane.getViewListSize();
        });
        return result;
    }
Public API

isExclusiveToPane

Checks whether a file is listed exclusively in the provided pane

File non-nullable File
- the file
Returns: {file:File,paneId:string}
    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;
    }
Public API

restoreAdjustedScrollState

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.

paneId non-nullable string
- id of the pane in which to adjust the scroll state, ALL_PANES or ACTIVE_PANE
heightDelta non-nullable number
- delta H to apply to the scroll state
    function restoreAdjustedScrollState(paneId, heightDelta) {
        _forEachPaneOrPanes(paneId, function (pane) {
            pane.restoreAndAdjustScrollState(_paneScrollStates[pane.id], heightDelta);
            delete _paneScrollStates[pane.id];
        });
    }
Public API

setActivePaneId

Switch active pane to the specified pane id (or ACTIVE_PANE/ALL_PANES, in which case this call does nothing).

paneId non-nullable string
- the id of the pane to activate
    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();
        }
    }
Public API

setLayoutScheme

Changes the layout scheme

rows non-nullable number
(may be 1 or 2)
columns non-nullable number
(may be 1 or 2)
summay
Rows or Columns may be 1 or 2 but both cannot be 2. 1x2, 2x1 or 1x1 are the legal values
    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;
    }
Public API

switchPaneFocus

Switch between panes

    function switchPaneFocus() {
        var $firstPane = $('#first-pane'), $secondPane = $('#second-pane');
        if($firstPane.hasClass('active-pane')) {
            $secondPane.click();
        }
        else {
            $firstPane.click();
        }
    }
Public API

traverseToNextViewByMRU

Get the next or previous file in the MRU list.

direction non-nullable number
- Must be 1 or -1 to traverse forward or backward
Returns: ?{file:File,paneId:string}
The File object of the next item in the traversal order or null if there aren't any files to traverse. May return current file if there are no other files to traverse.
    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);
    }
Public API

traverseToNextViewInListOrder

Get the next or previous file in list order.

direction non-nullable number
- Must be 1 or -1 to traverse forward or backward
Returns: ?{file:File,paneId:string}
The File object of the next item in the traversal order or null if there aren't any files to traverse. May return current file if there are no other files to traverse.
    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);
    }