Modules (188)

ProjectManager

Description

ProjectManager glues together the project model and file tree view and integrates as needed with other parts of Brackets. It is responsible for creating and updating the project tree when projects are opened and when changes occur to the file tree.

This module dispatches these events:

  • beforeProjectClose -- before _projectRoot changes, but working set files still open
  • projectClose -- just before _projectRoot changes; working set already cleared & project root unwatched
  • beforeAppClose -- before Brackets quits entirely
  • projectOpen -- after _projectRoot changes and the tree is re-rendered
  • projectRefresh -- when project tree is re-rendered for a reason other than a project being opened (e.g. from the Refresh command)

To listen for events, do something like this: (see EventDispatcher for details on this pattern) ProjectManager.on("eventname", handler);

Dependencies

Variables

Private

$projectTreeContainer

Type
jQueryObject
    var $projectTreeContainer;
Private

ERR_TYPE_CREATE Constant

Type
int
    var ERR_TYPE_CREATE                 = 1,
        ERR_TYPE_CREATE_EXISTS          = 2,
        ERR_TYPE_RENAME                 = 3,
        ERR_TYPE_DELETE                 = 4,
        ERR_TYPE_LOADING_PROJECT        = 5,
        ERR_TYPE_LOADING_PROJECT_NATIVE = 6,
        ERR_TYPE_MAX_FILES              = 7,
        ERR_TYPE_OPEN_DIALOG            = 8,
        ERR_TYPE_INVALID_FILENAME       = 9,
        ERR_TYPE_MOVE                   = 10;
Private

SETTINGS_FILENAME

Type
string
    var SETTINGS_FILENAME = "." + PreferencesManager.SETTINGS_FILENAME;

SORT_DIRECTORIES_FIRST

Name of the preferences for sorting directories first

Type
string
    var SORT_DIRECTORIES_FIRST = "sortDirectoriesFirst";
Private

_fileSystemChange

    var _fileSystemChange,
        _fileSystemRename,
        _showErrorDialog,
        _saveTreeState,
        renameItemInline,
        _renderTreeSync,
        _renderTree;
Private

_projectWarnedForTooManyFiles

Type
boolean
    var _projectWarnedForTooManyFiles = false;
Private

_refreshDelay Constant

Type
number
    var _refreshDelay = 1000;
Private

actionCreator

Type
ActionCreator
    var actionCreator = new ActionCreator(model);
Private

fileTreeViewContainer

Type
Element
    var fileTreeViewContainer;
Private

model

Type
ProjectModel.ProjectModel
    var model = new ProjectModel.ProjectModel({
        focused: _hasFileSelectionFocus()
    });

Functions

Private

_currentFileChange

e Object
jQuery event object
curFile File
Currently viewed file.
    function _currentFileChange(e, curFile) {
        actionCreator.setCurrentFile(curFile);
    }
Private

_displayCreationError

e $.Event
jQuery event object
errorInfo {type:any,isFolder:boolean}
Information passed in the error events
    function _displayCreationError(e, errorInfo) {
        window.setTimeout(function () {
            var error = errorInfo.type,
                isFolder = errorInfo.isFolder,
                name = errorInfo.name;

            if (error === FileSystemError.ALREADY_EXISTS) {
                _showErrorDialog(ERR_TYPE_CREATE_EXISTS, isFolder, null, name);
            } else if (error === ProjectModel.ERROR_INVALID_FILENAME) {
                _showErrorDialog(ERR_TYPE_INVALID_FILENAME, isFolder, ProjectModel._invalidChars);
            } else {
                var errString = error === FileSystemError.NOT_WRITABLE ?
                        Strings.NO_MODIFICATION_ALLOWED_ERR :
                        StringUtils.format(Strings.GENERIC_ERROR, error);

                _showErrorDialog(ERR_TYPE_CREATE, isFolder, errString, name).getPromise();
            }
        }, 10);
    }
Private

_documentSelectionFocusChange

    function _documentSelectionFocusChange() {
        var curFullPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE);
        if (curFullPath && _hasFileSelectionFocus()) {
            actionCreator.setSelected(curFullPath, true);
        } else {
            actionCreator.setSelected(null);
        }
        _fileViewControllerChange();
    }
Private

_fileViewControllerChange

    function _fileViewControllerChange() {
        actionCreator.setFocused(_hasFileSelectionFocus());
        _renderTree();
    }
Private

_getFallbackProjectPath

After failing to load a project, this function determines which project path to fallback to.

Returns: $.Promise
Promise that resolves to a project path {string}
    function _getFallbackProjectPath() {
        var fallbackPaths = [],
            recentProjects = PreferencesManager.getViewState("recentProjects") || [],
            deferred = new $.Deferred();

        // Build ordered fallback path array
        if (recentProjects.length > 1) {
            // *Most* recent project is the one that just failed to load, so use second most recent
            fallbackPaths.push(recentProjects[1]);
        }

        // Next is Getting Started project
        fallbackPaths.push(_getWelcomeProjectPath());

        // Helper func for Async.firstSequentially()
        function processItem(path) {
            var deferred = new $.Deferred(),
                fileEntry = FileSystem.getDirectoryForPath(path);

            fileEntry.exists(function (err, exists) {
                if (!err && exists) {
                    deferred.resolve();
                } else {
                    deferred.reject();
                }
            });

            return deferred.promise();
        }

        // Find first path that exists
        Async.firstSequentially(fallbackPaths, processItem)
            .done(function (fallbackPath) {
                deferred.resolve(fallbackPath);
            })
            .fail(function () {
                // Last resort is Brackets source folder which is guaranteed to exist
                deferred.resolve(FileUtils.getNativeBracketsDirectoryPath());
            });

        return deferred.promise();
    }
Private

_getProjectViewStateContext

    function _getProjectViewStateContext() {
        return { location : { scope: "user",
                             layer: "project",
                             layerID: model.projectRoot.fullPath } };
    }
Private

_getWelcomeProjectPath

sampleUrl string
URL for getting started project
initialPath string
Path to Brackets directory (see FileUtils.getNativeBracketsDirectoryPath())
Returns: !string
fullPath reference
    function _getWelcomeProjectPath() {
        return ProjectModel._getWelcomeProjectPath(Urls.GETTING_STARTED, FileUtils.getNativeBracketsDirectoryPath());
    }
Private

_hasFileSelectionFocus

Returns: boolean
`true` if the file tree has the focus
    function _hasFileSelectionFocus() {
        return FileViewController.getFileSelectionFocus() === FileViewController.PROJECT_MANAGER;
    }
Private

_loadProject

Loads the given folder as a project. Does NOT prompt about any unsaved changes - use openProject() instead to check for unsaved changes and (optionally) let the user choose the folder to open.

rootPath non-nullable string
Absolute path to the root folder of the project. A trailing "/" on the path is optional (unlike many Brackets APIs that assume a trailing "/").
isUpdating optional boolean
If true, indicates we're just updating the tree; if false, a different project is being loaded.
Returns: $.Promise
A promise object that will be resolved when the project is loaded and tree is rendered, or rejected if the project path fails to load.
    function _loadProject(rootPath, isUpdating) {
        var result = new $.Deferred(),
            startLoad = new $.Deferred();

        // Some legacy code calls this API with a non-canonical path
        rootPath = ProjectModel._ensureTrailingSlash(rootPath);

        var projectPrefFullPath = (rootPath + SETTINGS_FILENAME),
            file   = FileSystem.getFileForPath(projectPrefFullPath);

        //Verify that the project preferences file (.brackets.json) is NOT corrupted.
        //If corrupted, display the error message and open the file in editor for the user to edit.
        FileUtils.readAsText(file)
            .done(function (text) {
                try {
                    if (text) {
                        JSON.parse(text);
                    }
                } catch (err) {
                    // Cannot parse the text read from the project preferences file.
                    var info = MainViewManager.findInAllWorkingSets(projectPrefFullPath);
                    var paneId;
                    if (info.length) {
                        paneId = info[0].paneId;
                    }
                    FileViewController.openFileAndAddToWorkingSet(projectPrefFullPath, paneId)
                        .done(function () {
                            Dialogs.showModalDialog(
                                DefaultDialogs.DIALOG_ID_ERROR,
                                Strings.ERROR_PREFS_CORRUPT_TITLE,
                                Strings.ERROR_PROJ_PREFS_CORRUPT
                            ).done(function () {
                                // give the focus back to the editor with the pref file
                                MainViewManager.focusActivePane();
                            });
                        });
                }
            });

        if (isUpdating) {
            // We're just refreshing. Don't need to unwatch the project root, so we can start loading immediately.
            startLoad.resolve();
        } else {
            if (model.projectRoot && model.projectRoot.fullPath === rootPath) {
                return (new $.Deferred()).resolve().promise();
            }

            // About to close current project (if any)
            if (model.projectRoot) {
                exports.trigger("beforeProjectClose", model.projectRoot);
            }

            // close all the old files
            MainViewManager._closeAll(MainViewManager.ALL_PANES);

            _unwatchProjectRoot().always(function () {
                // Done closing old project (if any)
                if (model.projectRoot) {
                    LanguageManager._resetPathLanguageOverrides();
                    PreferencesManager._reloadUserPrefs(model.projectRoot);
                    exports.trigger("projectClose", model.projectRoot);
                }

                startLoad.resolve();
            });
        }

        startLoad.done(function () {
            var context = { location : { scope: "user",
                                         layer: "project" } };

            // Clear project path map
            if (!isUpdating) {
                PreferencesManager._stateProjectLayer.setProjectPath(rootPath);
            }

            // Populate file tree as long as we aren't running in the browser
            if (!brackets.inBrowser) {
                if (!isUpdating) {
                    _watchProjectRoot(rootPath);
                }
                // Point at a real folder structure on local disk
                var rootEntry = FileSystem.getDirectoryForPath(rootPath);
                rootEntry.exists(function (err, exists) {
                    if (exists) {
                        var projectRootChanged = (!model.projectRoot || !rootEntry) ||
                            model.projectRoot.fullPath !== rootEntry.fullPath;

                        // Success!
                        var perfTimerName = PerfUtils.markStart("Load Project: " + rootPath);

                        _projectWarnedForTooManyFiles = false;

                        _setProjectRoot(rootEntry).always(function () {
                            model.setBaseUrl(PreferencesManager.getViewState("project.baseUrl", context) || "");

                            if (projectRootChanged) {
                                _reloadProjectPreferencesScope();
                                PreferencesManager._setCurrentFile(rootPath);
                            }

                            // If this is the most current welcome project, record it. In future launches, we want
                            // to substitute the latest welcome project from the current build instead of using an
                            // outdated one (when loading recent projects or the last opened project).
                            if (rootPath === _getWelcomeProjectPath()) {
                                addWelcomeProjectPath(rootPath);
                            }

                            if (projectRootChanged) {
                                // Allow asynchronous event handlers to finish before resolving result by collecting promises from them
                                exports.trigger("projectOpen", model.projectRoot);
                                result.resolve();
                            } else {
                                exports.trigger("projectRefresh", model.projectRoot);
                                result.resolve();
                            }
                            PerfUtils.addMeasurement(perfTimerName);
                        });
                    } else {
                        console.log("error loading project");
                        _showErrorDialog(ERR_TYPE_LOADING_PROJECT_NATIVE, true, err || FileSystemError.NOT_FOUND, rootPath)
                            .done(function () {
                                // Reset _projectRoot to null so that the following _loadProject call won't
                                // run the 'beforeProjectClose' event a second time on the original project,
                                // which is now partially torn down (see #6574).
                                model.projectRoot = null;

                                // The project folder stored in preference doesn't exist, so load the default
                                // project directory.
                                // TODO (issue #267): When Brackets supports having no project directory
                                // defined this code will need to change
                                _getFallbackProjectPath().done(function (path) {
                                    _loadProject(path).always(function () {
                                        // Make sure not to reject the original deferred until the fallback
                                        // project is loaded, so we don't violate expectations that there is always
                                        // a current project before continuing after _loadProject().
                                        result.reject();
                                    });
                                });
                            });
                    }
                });
            }
        });

        return result.promise();
    }
Private

_projectSettings

Invoke project settings dialog.

Returns: $.Promise
    function _projectSettings() {
        return PreferencesDialogs.showProjectPreferencesDialog(getBaseUrl()).getPromise();
    }
Private

_reloadProjectPreferencesScope

    function _reloadProjectPreferencesScope() {
        var root = getProjectRoot();
        if (root) {
            // Alias the "project" Scope to the path Scope for the project-level settings file
            PreferencesManager._setProjectSettingsFile(root.fullPath + SETTINGS_FILENAME);
        } else {
            PreferencesManager._setProjectSettingsFile();
        }
    }
Private

_revertSelection

previousPath string,File
The previously selected path.
switchToWorkingSet boolean
True if we need to switch focus to the Working Set
    function _revertSelection(previousPath, switchToWorkingSet) {
        model.setSelected(previousPath);
        if (switchToWorkingSet) {
            FileViewController.setFileViewFocus(FileViewController.WORKING_SET_VIEW);
        }
    }
Private

_saveProjectPath

    var _saveProjectPath = function () {
        // save the current project
        PreferencesManager.setViewState("projectPath", model.projectRoot.fullPath);
    };
Private Public API

_setFileTreeSelectionWidth

width int
New width value
    function _setFileTreeSelectionWidth(width) {
        model.setSelectionWidth(width);
        _renderTreeSync();
    }

    // Initialize variables and listeners that depend on the HTML DOM
    AppInit.htmlReady(function () {
        $projectTreeContainer = $("#project-files-container");
        $projectTreeContainer.addClass("jstree jstree-brackets");
        $projectTreeContainer.css("overflow", "auto");
        $projectTreeContainer.css("position", "relative");

        fileTreeViewContainer = $("<div>").appendTo($projectTreeContainer)[0];

        model.setSelectionWidth($projectTreeContainer.width());

        $(".main-view").click(function (jqEvent) {
            if (!jqEvent.target.classList.contains("jstree-rename-input")) {
                forceFinishRename();
                actionCreator.setContext(null);
            }
        });

        $("#working-set-list-container").on("contentChanged", function () {
            $projectTreeContainer.trigger("contentChanged");
        });

        Menus.getContextMenu(Menus.ContextMenuIds.PROJECT_MENU).on("beforeContextMenuOpen", function () {
            actionCreator.restoreContext();
        });

        Menus.getContextMenu(Menus.ContextMenuIds.PROJECT_MENU).on("beforeContextMenuClose", function () {
            model.setContext(null, false, true);
        });

        $projectTreeContainer.on("contextmenu", function () {
            forceFinishRename();
        });

        $projectTreeContainer.on("dragover", function(e) {
            e.preventDefault();
        });

        // Add support for moving items to root directory
        $projectTreeContainer.on("drop", function(e) {
            var data = JSON.parse(e.originalEvent.dataTransfer.getData("text"));
            actionCreator.moveItem(data.path, getProjectRoot().fullPath);
            e.stopPropagation();
        });

        // When a context menu item is selected, we need to clear the context
        // because we don't get a beforeContextMenuClose event since Bootstrap
        // handles this directly.
        $("#project-context-menu").on("click.dropdown-menu", function () {
            model.setContext(null, true);
        });

        $projectTreeContainer.on("scroll", function () {
            // Close open menus on scroll and clear the context, but only if there's a menu open.
            if ($(".dropdown.open").length > 0) {
                Menus.closeAll();
                actionCreator.setContext(null);
            }
            // we need to render the tree without a delay to not cause selection extension issues (#10573)
            _renderTreeSync();
        });

        _renderTree();

        ViewUtils.addScrollerShadow($projectTreeContainer[0]);
    });

    EventDispatcher.makeEventDispatcher(exports);

    // Init default project path to welcome project
    PreferencesManager.stateManager.definePreference("projectPath", "string", _getWelcomeProjectPath());

    exports.on("projectOpen", _reloadProjectPreferencesScope);
    exports.on("projectOpen", _saveProjectPath);
    exports.on("beforeAppClose", _unwatchProjectRoot);

    // Due to circular dependencies, not safe to call on() directly for other modules' events
    EventDispatcher.on_duringInit(FileViewController, "documentSelectionFocusChange", _documentSelectionFocusChange);
    EventDispatcher.on_duringInit(FileViewController, "fileViewFocusChange", _fileViewControllerChange);
    EventDispatcher.on_duringInit(MainViewManager, "currentFileChange", _currentFileChange);

    // Commands
    CommandManager.register(Strings.CMD_OPEN_FOLDER,      Commands.FILE_OPEN_FOLDER,      openProject);
    CommandManager.register(Strings.CMD_PROJECT_SETTINGS, Commands.FILE_PROJECT_SETTINGS, _projectSettings);
    CommandManager.register(Strings.CMD_FILE_REFRESH,     Commands.FILE_REFRESH,          refreshFileTree);

    // Define the preference to decide how to sort the Project Tree files
    PreferencesManager.definePreference(SORT_DIRECTORIES_FIRST, "boolean", brackets.platform !== "mac", {
        description: Strings.DESCRIPTION_SORT_DIRECTORIES_FIRST
    })
        .on("change", function () {
            actionCreator.setSortDirectoriesFirst(PreferencesManager.get(SORT_DIRECTORIES_FIRST));
        });

    actionCreator.setSortDirectoriesFirst(PreferencesManager.get(SORT_DIRECTORIES_FIRST));
Private

_setProjectRoot

rootEntry Directory
directory object for the project root
Returns: $.Promise
resolved when the project is done setting up
    function _setProjectRoot(rootEntry) {
        var d = new $.Deferred();
        model.setProjectRoot(rootEntry).then(function () {
            d.resolve();
            model.reopenNodes(PreferencesManager.getViewState("project.treeState", _getProjectViewStateContext()));
        });
        return d.promise();
    }
Private

_unwatchProjectRoot

Returns: $.Promise
A promise that's resolved when the root is unwatched. Rejected if there is no project root or if the unwatch fails.
    function _unwatchProjectRoot() {
        var result = new $.Deferred();
        if (!model.projectRoot) {
            result.reject();
        } else {
            FileSystem.off("change", _fileSystemChange);
            FileSystem.off("rename", _fileSystemRename);

            FileSystem.unwatch(model.projectRoot, function (err) {
                if (err) {
                    console.error("Error unwatching project root: ", model.projectRoot.fullPath, err);
                    result.reject(err);
                } else {
                    result.resolve();
                }
            });

            // Reset allFiles cache
            model._resetCache();
        }

        return result.promise();
    }
Private

_watchProjectRoot

    function _watchProjectRoot(rootPath) {
        FileSystem.on("change", _fileSystemChange);
        FileSystem.on("rename", _fileSystemRename);

        FileSystem.watch(FileSystem.getDirectoryForPath(rootPath), ProjectModel._shouldShowName, ProjectModel.defaultIgnoreGlobs, function (err) {
            if (err === FileSystemError.TOO_MANY_ENTRIES) {
                if (!_projectWarnedForTooManyFiles) {
                    _showErrorDialog(ERR_TYPE_MAX_FILES);
                    _projectWarnedForTooManyFiles = true;
                }
            } else if (err) {
                console.error("Error watching project root: ", rootPath, err);
            }
        });

        // Reset allFiles cache
        model._resetCache();
    }
Public API

addClassesProvider

Adds a CSS class provider, invoked before each tree item is rendered.

callback non-nullable function(!{name:string, fullPath:string, isFile:boolean}):?string
* `name`: the file or directory name * `fullPath`: full path to the file or directory * `isFile`: true if it's a file, false if it's a directory Return a string containing space-separated CSS class(es) to add, or undefined to leave CSS unchanged.
    function addClassesProvider(callback) {
        return FileTreeView.addClassesProvider(callback);
    }
Public API

addIconProvider

Adds an icon provider. The callback is invoked before each tree item is rendered, and can return content to prepend to the item.

callback non-nullable function(!{name:string, fullPath:string, isFile:boolean}):?string, jQuery, DOMNode, Preact.DOM.ins
* `name`: the file or directory name * `fullPath`: full path to the file or directory * `isFile`: true if it's a file, false if it's a directory Return a string of HTML text, a Preact.DOM.ins instance, a jQuery object, or a DOM node; or undefined to prepend nothing.
    function addIconProvider(callback) {
        return FileTreeView.addIconProvider(callback);
    }

addWelcomeProjectPath

Adds the path to the list of welcome projects we've ever seen, if not on the list already.

path string
Path to possibly add
    function addWelcomeProjectPath(path) {
        var welcomeProjects = ProjectModel._addWelcomeProjectPath(path,
                                                                 PreferencesManager.getViewState("welcomeProjects"));
        PreferencesManager.setViewState("welcomeProjects", welcomeProjects);
    }
Public API

createNewItem

Create a new item in the current project.

{string|Directory} baseDir
Full path of the directory where the item should go. Defaults to the project root if the entry is not valid or not within the project.
{string} initialName
Initial name for the item
{boolean} skipRename
If true, don't allow the user to rename the item
{boolean} isFolder
If true, create a folder instead of a file
Returns: $.Promise
A promise object that will be resolved with the File of the created object, or rejected if the user cancelled or entered an illegal filename.
    function createNewItem(baseDir, initialName, skipRename, isFolder) {
        baseDir = model.getDirectoryInProject(baseDir);

        if (skipRename) {
            if(isFolder) {
                return model.createAtPath(baseDir + initialName + "/");
            }
            return model.createAtPath(baseDir + initialName);
        }
        return actionCreator.startCreating(baseDir, initialName, isFolder);
    }
Public API

deleteItem

Delete file or directore from project

entry non-nullable (File, Directory)
File or Directory to delete
    function deleteItem(entry) {
        var result = new $.Deferred();

        entry.moveToTrash(function (err) {
            if (!err) {
                DocumentManager.notifyPathDeleted(entry.fullPath);
                result.resolve();
            } else {
                _showErrorDialog(ERR_TYPE_DELETE, entry.isDirectory, FileUtils.getFileErrorString(err), entry.fullPath);

                result.reject(err);
            }
        });

        return result.promise();
    }
Public API

forceFinishRename

Causes the rename operation that's in progress to complete.

    function forceFinishRename() {
        actionCreator.performRename();
    }
Public API

getAllFiles

Returns an Array of all files for this project, optionally including files in the working set that are not under the project root. Files are filtered first by ProjectModel.shouldShow(), then by the custom filter argument (if one was provided).

filter optional function (File, number):boolean
Optional function to filter the file list (does not filter directory traversal). API matches Array.filter().
includeWorkingSet optional boolean
If true, include files in the working set that are not under the project root (*except* for untitled documents).
sort optional boolean
If true, The files will be sorted by their paths
Returns: $.Promise
Promise that is resolved with an Array of File objects.
    function getAllFiles(filter, includeWorkingSet, sort) {
        var viewFiles, deferred;

        // The filter and includeWorkingSet params are both optional.
        // Handle the case where filter is omitted but includeWorkingSet is
        // specified.
        if (includeWorkingSet === undefined && typeof (filter) !== "function") {
            includeWorkingSet = filter;
            filter = null;
        }

        if (includeWorkingSet) {
            viewFiles = MainViewManager.getWorkingSet(MainViewManager.ALL_PANES);
        }

        deferred = new $.Deferred();
        model.getAllFiles(filter, viewFiles, sort)
            .done(function (fileList) {
                deferred.resolve(fileList);
            })
            .fail(function (err) {
                if (err === FileSystemError.TOO_MANY_ENTRIES && !_projectWarnedForTooManyFiles) {
                    _showErrorDialog(ERR_TYPE_MAX_FILES);
                    _projectWarnedForTooManyFiles = true;
                }
                // resolve with empty list
                deferred.resolve([]);
            });
        return deferred.promise();
    }
Public API

getBaseUrl

Returns the encoded Base URL of the currently loaded project, or empty string if no project is open (during startup, or running outside of app shell).

Returns: String
    function getBaseUrl() {
        return model.getBaseUrl();
    }
Public API

getContext

Gets the filesystem object for the current context in the file tree.

    function getContext() {
        return model.getContext();
    }
Public API

getFileTreeContext

Returns the File or Directory corresponding to the item that was right-clicked on in the file tree menu.

Returns: ?(File,Directory)
    function getFileTreeContext() {
        var selectedEntry = model.getContext();
        return selectedEntry;
    }
Public API

getInitialProjectPath

Initial project path is stored in prefs, which defaults to the welcome project on first launch.

    function getInitialProjectPath() {
        return updateWelcomeProjectPath(PreferencesManager.getViewState("projectPath"));
    }
Public API

getLanguageFilter

Returns a filter for use with getAllFiles() that filters files based on LanguageManager language id

languageId non-nullable (string, Array.<string>)
a single string of a language id or an array of language ids
Returns: !function(File):boolean
    function getLanguageFilter(languageId) {
        return function languageFilter(file) {
            var id = LanguageManager.getLanguageForPath(file.fullPath).getId();
            if (typeof languageId === "string") {
                return (id === languageId);
            } else {
                return (languageId.indexOf(id) !== -1);
            }
        };
    }
Public API

getProjectRoot

Returns the root folder of the currently loaded project, or null if no project is open (during startup, or running outside of app shell).

Returns: Directory
    function getProjectRoot() {
        return model.projectRoot;
    }
Public API

getSelectedItem

Returns the File or Directory corresponding to the item selected in the sidebar panel, whether in the file tree OR in the working set; or null if no item is selected anywhere in the sidebar. May NOT be identical to the current Document - a folder may be selected in the sidebar, or the sidebar may not have the current document visible in the tree & working set.

Returns: ?(File,Directory)
    function getSelectedItem() {
        // Prefer file tree context, then file tree selection, else use working set
        var selectedEntry = getFileTreeContext();
        if (!selectedEntry) {
            selectedEntry = model.getSelected();
        }
        if (!selectedEntry) {
            selectedEntry = MainViewManager.getCurrentlyViewedFile();
        }
        return selectedEntry;
    }
Public API

isWelcomeProjectPath

Returns true if the given path is the same as one of the welcome projects we've previously opened, or the one for the current build.

path string
Path to check to see if it's a welcome project path
Returns: boolean
true if this is a welcome project path
    function isWelcomeProjectPath(path) {
        return ProjectModel._isWelcomeProjectPath(path, _getWelcomeProjectPath(), PreferencesManager.getViewState("welcomeProjects"));
    }
Public API

isWithinProject

Returns true if absPath lies within the project, false otherwise. Does not support paths containing ".."

absPathOrEntry string,FileSystemEntry
Returns: boolean
    function isWithinProject(absPathOrEntry) {
        return model.isWithinProject(absPathOrEntry);
    }
Public API

makeProjectRelativeIfPossible

If absPath lies within the project, returns a project-relative path. Else returns absPath unmodified. Does not support paths containing ".."

absPath non-nullable string
Returns: !string
    function makeProjectRelativeIfPossible(absPath) {
        return model.makeProjectRelativeIfPossible(absPath);
    }
Public API

openProject

Open a new project. Currently, Brackets must always have a project open, so this method handles both closing the current project and opening a new project.

path optional string
Optional absolute path to the root folder of the project. If path is undefined or null, displays a dialog where the user can choose a folder to load. If the user cancels the dialog, nothing more happens.
Returns: $.Promise
A promise object that will be resolved when the project is loaded and tree is rendered, or rejected if the project path fails to load.
    function openProject(path) {

        var result = new $.Deferred();

        // Confirm any unsaved changes first. We run the command in "prompt-only" mode, meaning it won't
        // actually close any documents even on success; we'll do that manually after the user also oks
        // the folder-browse dialog.
        CommandManager.execute(Commands.FILE_CLOSE_ALL, { promptOnly: true })
            .done(function () {
                if (path) {
                    // use specified path
                    _loadProject(path, false).then(result.resolve, result.reject);
                } else {
                    // Pop up a folder browse dialog
                    FileSystem.showOpenDialog(false, true, Strings.CHOOSE_FOLDER, model.projectRoot.fullPath, null, function (err, files) {
                        if (!err) {
                            // If length == 0, user canceled the dialog; length should never be > 1
                            if (files.length > 0) {
                                // Load the new project into the folder tree
                                _loadProject(files[0]).then(result.resolve, result.reject);
                            } else {
                                result.reject();
                            }
                        } else {
                            _showErrorDialog(ERR_TYPE_OPEN_DIALOG, null, err);
                            result.reject();
                        }
                    });
                }
            })
            .fail(function () {
                result.reject();
            });

        // if fail, don't open new project: user canceled (or we failed to save its unsaved changes)
        return result.promise();
    }
Public API

refreshFileTree

Refresh the project's file tree, maintaining the current selection.

Note that the original implementation of this returned a promise to be resolved when the refresh is complete. That use is deprecated and refreshFileTree is now a "fire and forget" kind of function.

    var refreshFileTree = function refreshFileTree() {
        FileSystem.clearAllCaches();
        return new $.Deferred().resolve().promise();
    };

    refreshFileTree = _.debounce(refreshFileTree, _refreshDelay);
Public API

rerenderTree

Forces the file tree to rerender. Typically, the tree only rerenders the portions of the tree that have changed data. If an extension that augments the tree has changes that it needs to display, calling rerenderTree will cause the components for the whole tree to be rerendered.

    function rerenderTree() {
        _renderTree(true);
    }


    // Private API helpful in testing
    exports._actionCreator                = actionCreator;
    exports._RENDER_DEBOUNCE_TIME         = _RENDER_DEBOUNCE_TIME;

    // Private API for use with SidebarView
    exports._setFileTreeSelectionWidth    = _setFileTreeSelectionWidth;

    // Define public API
    exports.getProjectRoot                = getProjectRoot;
    exports.getBaseUrl                    = getBaseUrl;
    exports.setBaseUrl                    = setBaseUrl;
    exports.isWithinProject               = isWithinProject;
    exports.makeProjectRelativeIfPossible = makeProjectRelativeIfPossible;
    exports.shouldShow                    = ProjectModel.shouldShow;
    exports.openProject                   = openProject;
    exports.getFileTreeContext            = getFileTreeContext;
    exports.getSelectedItem               = getSelectedItem;
    exports.getContext                    = getContext;
    exports.getInitialProjectPath         = getInitialProjectPath;
    exports.isWelcomeProjectPath          = isWelcomeProjectPath;
    exports.updateWelcomeProjectPath      = updateWelcomeProjectPath;
    exports.createNewItem                 = createNewItem;
    exports.renameItemInline              = renameItemInline;
    exports.deleteItem                    = deleteItem;
    exports.forceFinishRename             = forceFinishRename;
    exports.showInTree                    = showInTree;
    exports.refreshFileTree               = refreshFileTree;
    exports.getAllFiles                   = getAllFiles;
    exports.getLanguageFilter             = getLanguageFilter;
    exports.addIconProvider               = addIconProvider;
    exports.addClassesProvider            = addClassesProvider;
    exports.rerenderTree                  = rerenderTree;
});
Public API

setBaseUrl

Sets the encoded Base URL of the currently loaded project.

String
    function setBaseUrl(projectBaseUrl) {
        var context = _getProjectViewStateContext();

        projectBaseUrl = model.setBaseUrl(projectBaseUrl);

        PreferencesManager.setViewState("project.baseUrl", projectBaseUrl, context);
    }
Public API

showInTree

Expands tree nodes to show the given file or folder and selects it. Silently no-ops if the path lies outside the project, or if it doesn't exist.

entry non-nullable (File, Directory)
File or Directory to show
Returns: $.Promise
Resolved when done; or rejected if not found
    function showInTree(entry) {
        return model.showInTree(entry).then(_saveTreeState);
    }
Public API

updateWelcomeProjectPath

If the provided path is to an old welcome project, returns the current one instead.

    function updateWelcomeProjectPath(path) {
        if (isWelcomeProjectPath(path)) {
            return _getWelcomeProjectPath();
        } else {
            return path;
        }
    }

Classes

Constructor

ActionCreator

model ProjectModel
store (in Flux terminology) with the project data
    function ActionCreator(model) {
        this.model = model;
        this._bindEvents();
    }

Methods

Private

_bindEvents

    ActionCreator.prototype._bindEvents = function () {

        // Change events are the standard Flux signal to rerender the view. Note that
        // current Flux style is to have the view itself listen to the Store for change events
        // and re-render itself.
        this.model.on(ProjectModel.EVENT_CHANGE, function () {
            _renderTree();
        });

        // The "should select" event signals that we need to open the document based on file tree
        // activity.
        this.model.on(ProjectModel.EVENT_SHOULD_SELECT, function (e, data) {
            if (data.add) {
                FileViewController.openFileAndAddToWorkingSet(data.path).fail(_.partial(_revertSelection, data.previousPath, !data.hadFocus));
            } else {
                FileViewController.openAndSelectDocument(data.path, FileViewController.PROJECT_MANAGER).fail(_.partial(_revertSelection, data.previousPath, !data.hadFocus));
            }
        });

        this.model.on(ProjectModel.EVENT_SHOULD_FOCUS, function () {
            FileViewController.setFileViewFocus(FileViewController.PROJECT_MANAGER);
        });

        this.model.on(ProjectModel.ERROR_CREATION, _displayCreationError);
    };

cancelRename

See ProjectModel.cancelRename

    ActionCreator.prototype.cancelRename = function () {
        this.model.cancelRename();
    };

closeSubtree

See ProjectModel.closeSubtree

    ActionCreator.prototype.closeSubtree = function (path) {
        this.model.closeSubtree(path);
        _saveTreeState();
    };

    ActionCreator.prototype.dragItem = function (path) {
        // Close open menus on drag and clear the context, but only if there's a menu open.
        if ($(".dropdown.open").length > 0) {
            Menus.closeAll();
            this.setContext(null);
        }

        // Close directory, if dragged item is directory
        if (_.last(path) === '/') {
            this.setDirectoryOpen(path, false);
        }
    };

moveItem

Moves the item in the oldPath to the newDirectory directory

    ActionCreator.prototype.moveItem = function (oldPath, newDirectory) {
        var fileName = FileUtils.getBaseName(oldPath),
            newPath = newDirectory + fileName,
            self = this;

        // If item dropped onto itself or onto its parent directory, return
        if (oldPath === newDirectory || FileUtils.getParentPath(oldPath) === newDirectory) {
            return;
        }

        // Add trailing slash if directory is moved
        if (_.last(oldPath) === '/') {
            newPath = ProjectModel._ensureTrailingSlash(newPath);
        }

        this.startRename(oldPath, true);
        this.setRenameValue(newPath);

        this.performRename();
        this.setDirectoryOpen(newDirectory, true);
    };

performRename

See ProjectModel.performRename

    ActionCreator.prototype.performRename = function () {
        return this.model.performRename();
    };

refresh

See ProjectModel.refresh

    ActionCreator.prototype.refresh = function () {
        this.model.refresh();
    };

restoreContext

See ProjectModel.restoreContext

    ActionCreator.prototype.restoreContext = function () {
        this.model.restoreContext();
    };

selectInWorkingSet

See ProjectModel.selectInWorkingSet

    ActionCreator.prototype.selectInWorkingSet = function (path) {
        this.model.selectInWorkingSet(path);
    };

setContext

See ProjectModel.setContext

    ActionCreator.prototype.setContext = function (path) {
        this.model.setContext(path);
    };

setCurrentFile

See ProjectModel.setCurrentFile

    ActionCreator.prototype.setCurrentFile = function (curFile) {
        this.model.setCurrentFile(curFile);
    };

setDirectoryOpen

Sets the directory at the given path to open in the tree and saves the open nodes to view state.

See ProjectModel.setDirectoryOpen

    ActionCreator.prototype.setDirectoryOpen = function (path, open) {
        this.model.setDirectoryOpen(path, open).then(_saveTreeState);
    };

setFocused

See ProjectModel.setFocused

    ActionCreator.prototype.setFocused = function (focused) {
        this.model.setFocused(focused);
    };

setRenameValue

See ProjectModel.setRenameValue

    ActionCreator.prototype.setRenameValue = function (path) {
        this.model.setRenameValue(path);
    };

setSelected

See ProjectModel.setSelected

    ActionCreator.prototype.setSelected = function (path, doNotOpen) {
        this.model.setSelected(path, doNotOpen);
    };

setSortDirectoriesFirst

See ProjectModel.setSortDirectoriesFirst

    ActionCreator.prototype.setSortDirectoriesFirst = function (sortDirectoriesFirst) {
        this.model.setSortDirectoriesFirst(sortDirectoriesFirst);
    };

startCreating

See ProjectModel.startCreating

    ActionCreator.prototype.startCreating = function (basedir, newName, isFolder) {
        return this.model.startCreating(basedir, newName, isFolder);
    };

startRename

See ProjectModel.startRename

    ActionCreator.prototype.startRename = function (path, isMoved) {
        // This is very not Flux-like, which is a sign that Flux may not be the
        // right choice here *or* that this architecture needs to evolve subtly
        // in how errors are reported (more like the create case).
        // See #9284.
        renameItemInline(path, isMoved);
    };

toggleSubdirectories

See ProjectModel.toggleSubdirectories

    ActionCreator.prototype.toggleSubdirectories = function (path, openOrClose) {
        this.model.toggleSubdirectories(path, openOrClose).then(_saveTreeState);
    };