$projectTreeContainer.on("contextmenu", function () {

$projectTreeContainer.on("scroll", function () {

// Close open menus on scroll and clear the context, but only if there's a menu open.

// When a context menu item is selected, we need to clear the context

// because we don't get a beforeContextMenuClose event since Bootstrap

$("#project-context-menu").on("click.dropdown-menu", function () {

/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/* unittests: ProjectModel */
/*global define, brackets, $ */

/**
 * Provides the data source for a project and manages the view model for the FileTreeView.
 */
define(function (require, exports, module) {
    "use strict";

    var InMemoryFile        = require("document/InMemoryFile"),
        FileUtils           = require("file/FileUtils"),
        _                   = require("thirdparty/lodash"),
        FileSystem          = require("filesystem/FileSystem"),
        FileSystemError     = require("filesystem/FileSystemError"),
        FileTreeViewModel   = require("project/FileTreeViewModel"),
        Async               = require("utils/Async");

    // Constants
    var EVENT_CHANGE            = "change",
        EVENT_SHOULD_SELECT     = "select",
        EVENT_SHOULD_FOCUS      = "focus",
        ERROR_CREATION          = "creationError",
        ERROR_INVALID_FILENAME  = "invalidFilename";

    /**
     * @private
     * File and folder names which are not displayed or searched
     * TODO: We should add the rest of the file names that TAR excludes:
     *    http://www.gnu.org/software/tar/manual/html_section/exclude.html
     * TODO: This should be user configurable
     *    https://github.com/adobe/brackets/issues/6781
     * @type {RegExp}
     */
    var _exclusionListRegEx = /\.pyc$|^\.git$|^\.gitmodules$|^\.svn$|^\.DS_Store$|^Thumbs\.db$|^\.hg$|^CVS$|^\.hgtags$|^\.idea$|^\.c9revisions$|^\.SyncArchive$|^\.SyncID$|^\.SyncIgnore$|\~$/;

    /**
     * @private
     * A string containing all invalid characters for a specific platform.
     * This will be used to construct a regular expression for checking invalid filenames.
     * When a filename with one of these invalid characters are detected, then it is
     * also used to substitute the place holder of the error message.
     */
    var _invalidChars;

    /**
     * @private
     * RegEx to validate if a filename is not allowed even if the system allows it.
     * This is done to prevent cross-platform issues.
     */

    var _illegalFilenamesRegEx = /^(\.+|com[1-9]|lpt[1-9]|nul|con|prn|aux|)$|\.+$/i;

    /**
     * Returns true if this matches valid filename specifications.
     *
     * TODO: This likely belongs in FileUtils.
     *
     * @param {string} filename to check
     * @param {string} invalidChars List of characters that are disallowed
     * @return {boolean} true if the filename is valid
     */
    function isValidFilename(filename, invalidChars) {
        // Validate file name
        // Checks for valid Windows filenames:
        // See http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
        return !((filename.search(new RegExp("[" + invalidChars + "]+")) !== -1) ||
                 filename.match(_illegalFilenamesRegEx));
    }

    /**
     * @private
     * See shouldShow
     */
    function _shouldShowName(name) {
        return !name.match(_exclusionListRegEx);
    }

    /**
     * Returns false for files and directories that are not commonly useful to display.
     *
     * @param {!FileSystemEntry} entry File or directory to filter
     * @return {boolean} true if the file should be displayed
     */
    function shouldShow(entry) {
        return _shouldShowName(entry.name);
    }

    // Constants used by the ProjectModel

    var FILE_RENAMING     = 0,
        FILE_CREATING     = 1,
        RENAME_CANCELLED  = 2;

    /**
     * @private
     *
     * Determines if a path string is pointing to a directory (does it have a trailing slash?)
     *
     * @param {string} path Path to test.
     */
    function _pathIsFile(path) {
        return _.last(path) !== "/";
    }

    /**
     * @private
     *
     * Gets the FileSystem object (either a File or Directory) based on the path provided.
     *
     * @param {string} path Path to retrieve
     */
    function _getFSObject(path) {
        if (!path) {
            return path;
        } else if (_pathIsFile(path)) {
            return FileSystem.getFileForPath(path);
        }
        return FileSystem.getDirectoryForPath(path);
    }

    /**
     * @private
     *
     * Given what is possible a FileSystem object, return its path (if a string path is passed in,
     * it will be returned as-is).
     *
     * @param {FileSystemEntry} fsobj Object from which the path should be extracted
     */
    function _getPathFromFSObject(fsobj) {
        if (fsobj && fsobj.fullPath) {
            return fsobj.fullPath;
        }
        return fsobj;
    }

    /**
     * Creates a new file or folder at the given path. The returned promise is rejected if the filename
     * is invalid, the new path already exists or some other filesystem error comes up.
     *
     * @param {string} path path to create
     * @param {boolean} isFolder true if the new entry is a folder
     * @return {$.Promise} resolved when the file or directory has been created.
     */
    function doCreate(path, isFolder) {
        var d = new $.Deferred();

        var name = FileUtils.getBaseName(path);
        if (!isValidFilename(name, _invalidChars)) {
            return d.reject(ERROR_INVALID_FILENAME).promise();
        }

        FileSystem.resolve(path, function (err) {
            if (!err) {
                // Item already exists, fail with error
                d.reject(FileSystemError.ALREADY_EXISTS);
            } else {
                if (isFolder) {
                    var directory = FileSystem.getDirectoryForPath(path);

                    directory.create(function (err) {
                        if (err) {
                            d.reject(err);
                        } else {
                            d.resolve(directory);
                        }
                    });
                } else {
                    // Create an empty file
                    var file = FileSystem.getFileForPath(path);

                    FileUtils.writeText(file, "").then(function () {
                        d.resolve(file);
                    }, d.reject);
                }
            }
        });

        return d.promise();
    }

    /**
     * @constructor
     *
     * The ProjectModel provides methods for accessing information about the current open project.
     * It also manages the view model to display a FileTreeView of the project.
     */
    function ProjectModel(initial) {
        initial = initial || {};
        if (initial.projectRoot) {
            this.projectRoot = initial.projectRoot;
        }

        if (initial.focused !== undefined) {
            this._focused = initial.focused;
        }
        this._viewModel = new FileTreeViewModel.FileTreeViewModel();
        this._viewModel.on(FileTreeViewModel.EVENT_CHANGE, function () {
            $(this).trigger(EVENT_CHANGE);
        }.bind(this));
        this._selections = {};
    }

    /**
     * @type {Directory}
     *
     * The root Directory object for the project.
     */
    ProjectModel.prototype.projectRoot = null;

    /**
     * @private
     * @type {FileTreeViewModel}
     *
     * The view model for this project.
     */
    ProjectModel.prototype._viewModel = null;

    /**
     * @private
     * @type {string}
     *
     * Encoded URL
     * @see getBaseUrl(), setBaseUrl()
     */
    ProjectModel.prototype._projectBaseUrl = "";

    /**
     * @private
     * @type {{selected: ?string, context: ?string, previousContext: ?string, rename: ?Object}}
     *
     * Keeps track of selected files, context, previous context and files
     * that are being renamed or created.
     */
    ProjectModel.prototype._selections = null;

    /**
     * @private
     * @type {boolean}
     *
     * Flag to store whether the file tree has focus.
     */
    ProjectModel.prototype._focused = true;

    /**
     * @private
     * @type {string}
     *
     * Current file path being viewed.
     */
    ProjectModel.prototype._currentPath = null;

    /**
     * @private
     * @type {?$.Promise.<Array<File>>}
     *
     * A promise that is resolved with an array of all project files. Used by
     * ProjectManager.getAllFiles().
     */
    ProjectModel.prototype._allFilesCachePromise = null;

    /**
     * Sets whether the file tree is focused or not.
     *
     * @param {boolean} focused True if the file tree has the focus.
     */
    ProjectModel.prototype.setFocused = function (focused) {
        this._focused = focused;
        if (!focused) {
            this.setSelected(null);
        }
    };

    /**
     * Sets the width of the selection bar.
     *
     * @param {int} width New width
     */
    ProjectModel.prototype.setSelectionWidth = function (width) {
        this._viewModel.setSelectionWidth(width);
    };

    /**
     * Tracks the scroller position.
     *
     * @param {int} scrollTop Top of scroll position
     * @param {int} scrollLeft Left of scroll position
     * @param {int} offsetTop Top of scroller element
     */
    ProjectModel.prototype.setScrollerInfo = function (scrollTop, scrollLeft, offsetTop) {
        this._viewModel.setSelectionScrollerInfo(scrollTop, scrollLeft, offsetTop);
    };

    /**
     * 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).
     * @return {String}
     */
    ProjectModel.prototype.getBaseUrl = function getBaseUrl() {
        return this._projectBaseUrl;
    };

    /**
     * Sets the encoded Base URL of the currently loaded project.
     * @param {String}
     */
    ProjectModel.prototype.setBaseUrl = function setBaseUrl(projectBaseUrl) {
        // Ensure trailing slash to be consistent with projectRoot.fullPath
        // so they're interchangable (i.e. easy to convert back and forth)
        if (projectBaseUrl.length > 0 && projectBaseUrl[projectBaseUrl.length - 1] !== "/") {
            projectBaseUrl += "/";
        }

        this._projectBaseUrl = projectBaseUrl;
        return projectBaseUrl;
    };

    /**
     * Returns true if absPath lies within the project, false otherwise.
     * Does not support paths containing ".."
     *
     * @param {string|FileSystemEntry} absPathOrEntry
     * @return {boolean}
     */
    ProjectModel.prototype.isWithinProject = function isWithinProject(absPathOrEntry) {
        var absPath = absPathOrEntry.fullPath || absPathOrEntry;
        return (this.projectRoot && absPath.indexOf(this.projectRoot.fullPath) === 0);
    };

    /**
     * If absPath lies within the project, returns a project-relative path. Else returns absPath
     * unmodified.
     * Does not support paths containing ".."
     *
     * @param {!string} absPath
     * @return {!string}
     */
    ProjectModel.prototype.makeProjectRelativeIfPossible = function makeProjectRelativeIfPossible(absPath) {
        if (absPath && this.isWithinProject(absPath)) {
            return absPath.slice(this.projectRoot.fullPath.length);
        }
        return absPath;
    };

    /**
     * @private
     *
     * Returns a promise that resolves with a cached copy of all project files.
     * Used by ProjectManager.getAllFiles(). Ensures that at most one un-cached
     * directory traversal is active at a time, which is useful at project load
     * time when watchers (and hence filesystem-level caching) has not finished
     * starting up. The cache is cleared on every filesystem change event, and
     * also on project load and unload.
     *
     * @return {$.Promise.<Array.<File>>}
     */
    ProjectModel.prototype._getAllFilesCache = function _getAllFilesCache() {
        if (!this._allFilesCachePromise) {
            var deferred = new $.Deferred(),
                allFiles = [],
                allFilesVisitor = function (entry) {
                    if (shouldShow(entry)) {
                        if (entry.isFile) {
                            allFiles.push(entry);
                        }
                        return true;
                    }
                    return false;
                };

            this._allFilesCachePromise = deferred.promise();

            this.projectRoot.visit(allFilesVisitor, function (err) {
                if (err) {
                    deferred.reject(err);
                } else {
                    deferred.resolve(allFiles);
                }
            }.bind(this));
        }

        return this._allFilesCachePromise;
    };

    /**
     * Returns an Array of all files for this project, optionally including
     * files additional files provided. Files are filtered out by shouldShow().
     *
     * @param {function (File, number):boolean=} filter Optional function to filter
     *          the file list (does not filter directory traversal). API matches Array.filter().
     * @param {Array.<File>=} additionalFiles Additional files to include (for example, the WorkingSet)
     *          Only adds files that are *not* under the project root or untitled documents.
     *
     * @return {$.Promise} Promise that is resolved with an Array of File objects.
     */
    ProjectModel.prototype.getAllFiles = function getAllFiles(filter, additionalFiles) {
        // The filter and includeWorkingSet params are both optional.
        // Handle the case where filter is omitted but includeWorkingSet is
        // specified.
        if (additionalFiles === undefined && typeof (filter) !== "function") {
            additionalFiles = filter;
            filter = null;
        }

        var filteredFilesDeferred = new $.Deferred();

        // First gather all files in project proper
        // Note that with proper promises we may be able to fix this so that we're not doing this
        // anti-pattern of creating a separate deferred rather than just chaining off of the promise
        // from _getAllFilesCache
        this._getAllFilesCache().done(function (result) {
            // Add working set entries, if requested
            if (additionalFiles) {
                additionalFiles.forEach(function (file) {
                    if (result.indexOf(file) === -1 && !(file instanceof InMemoryFile)) {
                        result.push(file);
                    }
                });
            }

            // Filter list, if requested
            if (filter) {
                result = result.filter(filter);
            }

            // If a done handler attached to the returned filtered files promise
            // throws an exception that isn't handled here then it will leave
            // _allFilesCachePromise in an inconsistent state such that no
            // additional done handlers will ever be called!
            try {
                filteredFilesDeferred.resolve(result);
            } catch (e) {
                console.warn("Unhandled exception in getAllFiles handler: ", e);
            }
        }).fail(function (err) {
            try {
                filteredFilesDeferred.resolve([]);
            } catch (e) {
                console.warn("Unhandled exception in getAllFiles handler: ", e);
            }
        });

        return filteredFilesDeferred.promise();
    };

    /**
     * @private
     *
     * Resets the all files cache.
     */
    ProjectModel.prototype._resetCache = function _resetCache() {
        this._allFilesCachePromise = null;
    };

    /**
     * Adds an event listener for this ProjectModel. See jQuery's documentation for .on.
     *
     * Available events:
     *
     * * EVENT_CHANGE (`change`) - Fired when there's a change that should refresh the UI
     * * EVENT_SHOULD_SELECT (`select`) - Specifies that a selection has been made in the file tree and that the file tree should be selected
     * * ERROR_CREATION (`creationError`) - Triggered when there is a problem creating a file.
     */
    ProjectModel.prototype.on = function (event, handler) {
        $(this).on(event, handler);
    };

    /**
     * Removes an event listener for this ProjectModel. See jQuery's documentation for .off.
     */
    ProjectModel.prototype.off = function (event, handler) {
        $(this).off(event, handler);
    };

    /**
     * Sets the project root (effectively resetting this ProjectModel).
     *
     * @param {Directory} projectRoot new project root
     * @return {$.Promise} resolved when the project root has been updated
     */
    ProjectModel.prototype.setProjectRoot = function (projectRoot) {
        this.projectRoot = projectRoot;
        this._resetCache();
        this._viewModel._rootChanged();

        var d = new $.Deferred(),
            self = this;

        projectRoot.getContents(function (err, contents) {
            if (err) {
                d.reject(err);
            } else {
                self._viewModel.setDirectoryContents("", contents);
                d.resolve();
            }
        });
        return d.promise();
    };

    /**
     * @private
     *
     * Gets the contents of a directory at the given path.
     *
     * @param {string} path path to retrieve
     * @return {$.Promise} Resolved with the directory contents.
     */
    ProjectModel.prototype._getDirectoryContents = function (path) {
        var d = new $.Deferred();
        FileSystem.getDirectoryForPath(path).getContents(function (err, contents) {
            if (err) {
                d.reject(err);
            } else {
                d.resolve(contents);
            }
        });
        return d.promise();
    };

    /**
     * Opens or closes the given directory in the file tree.
     *
     * @param {string} path Path to open
     * @param {boolean} open `true` to open the path
     * @return {$.Promise} resolved when the path has been opened.
     */
    ProjectModel.prototype.setDirectoryOpen = function (path, open) {
        var projectRelative = this.makeProjectRelativeIfPossible(path),
            needsLoading    = !this._viewModel.isPathLoaded(projectRelative),
            d               = new $.Deferred(),
            self            = this;

        function onSuccess(contents) {
            // Update the view model
            if (contents) {
                self._viewModel.setDirectoryContents(projectRelative, contents);
            }

            if (open) {
                self._viewModel.openPath(projectRelative);
                if (self._focused) {
                    var currentPathInProject = self.makeProjectRelativeIfPossible(self._currentPath);
                    if (self._viewModel.isFilePathVisible(currentPathInProject)) {
                        self.setSelected(self._currentPath, true);
                    } else {
                        self.setSelected(null);
                    }
                }
            } else {
                self._viewModel.setDirectoryOpen(projectRelative, false);
                var selected = self._selections.selected;
                if (selected) {
                    var relativeSelected = self.makeProjectRelativeIfPossible(selected);
                    if (!self._viewModel.isFilePathVisible(relativeSelected)) {
                        self.setSelected(null);
                    }
                }
            }

            d.resolve();
        }

        // If the view model doesn't have the data it needs, we load it now, otherwise we can just
        // manage the selection and resovle the promise.
        if (open && needsLoading) {
            var parentDirectory = FileUtils.getDirectoryPath(FileUtils.stripTrailingSlash(path));
            this.setDirectoryOpen(parentDirectory, true).then(function () {
                self._getDirectoryContents(path).then(onSuccess).fail(function (err) {
                    d.reject(err);
                });
            }, function (err) {
                d.reject(err);
            });
        } else {
            onSuccess();
        }

        return d.promise();
    };

    /**
     * Shows the given path in the tree and selects it if it's a file. Any intermediate directories
     * will be opened and a promise is returned to show when the entire operation is complete.
     *
     * @param {string|File|Directory} path full path to the file or directory
     * @return {$.Promise} promise resolved when the path is shown
     */
    ProjectModel.prototype.showInTree = function (path) {
        var d = new $.Deferred();
        path = _getPathFromFSObject(path);

        var projectRelative = this.makeProjectRelativeIfPossible(path);

        // Not in project?
        if (projectRelative[0] === "/") {
            d.resolve();
        } else {
            var parentDirectory = FileUtils.getDirectoryPath(path),
                self = this;
            this.setDirectoryOpen(parentDirectory, true).then(function () {
                if (_pathIsFile(path)) {
                    self.setSelected(path);
                }
                d.resolve();
            }, function (err) {
                d.reject(err);
            });
        }
        return d.promise();
    };

    /**
     * Selects the given path in the file tree and opens the file (unless doNotOpen is specified).
     * Directories will not be selected.
     *
     * When the selection changes, any rename operation that is currently underway will be completed.
     *
     * @param {string} path full path to the file being selected
     * @param {boolean} doNotOpen `true` if the file should not be opened.
     */
    ProjectModel.prototype.setSelected = function (path, doNotOpen) {
        path = _getPathFromFSObject(path);

        // Directories are not selectable
        if (!_pathIsFile(path)) {
            return;
        }

        this.performRename();

        var oldProjectPath = this.makeProjectRelativeIfPossible(this._selections.selected),
            pathInProject = this.makeProjectRelativeIfPossible(path);

        this._viewModel.moveMarker("selected", oldProjectPath, pathInProject);
        if (this._selections.context) {
            this._viewModel.moveMarker("context", this.makeProjectRelativeIfPossible(this._selections.context), null);
            delete this._selections.context;
        }
        this._selections.selected = path;

        if (path) {
            $(this).trigger(EVENT_SHOULD_FOCUS);

            if (!doNotOpen) {
                $(this).trigger(EVENT_SHOULD_SELECT, {
                    path: path
                });
            }
        }
    };

    /**
     * Gets the currently selected file or directory.
     *
     * @return {FileSystemEntry} the filesystem object for the currently selected file
     */
    ProjectModel.prototype.getSelected = function () {
        return _getFSObject(this._selections.selected);
    };

    /**
     * Keeps track of which file is currently being edited.
     *
     * @param {File|string} curFile Currently edited file.
     */
    ProjectModel.prototype.setCurrentFile = function (curFile) {
        this._currentPath = _getPathFromFSObject(curFile);
    };

    /**
     * Adds the file at the given path to the Working Set and selects it there.
     *
     * @param {string} path full path of file to open in Working Set
     */
    ProjectModel.prototype.selectInWorkingSet = function (path) {
        this.performRename();
        $(this).trigger(EVENT_SHOULD_SELECT, {
            path: path,
            add: true
        });
    };

    /**
     * Sets the context (for context menu operations) to the given path. This is independent from the
     * open/selected file.
     *
     * @param {string} path full path of file or directory to which the context should be setBaseUrl
     */
    ProjectModel.prototype.setContext = function (path) {
        // This bit is not ideal: when the user right-clicks on an item in the file tree
        // and there is already a context menu up, the FileTreeView sends a signal to set the
        // context to the new element but the PopupManager follows that with a message that it's
        // closing the context menu (because it closes the previous one and then opens the new
        // one.) This timing means that we need to provide some special case handling here.
        if (!path) {
            this._selections.previousContext = this._selections.context;
        } else {
            this._selections.previousContext = path;
        }

        path = _getPathFromFSObject(path);
        this.performRename();
        var currentContext = this._selections.context;
        this._selections.context = path;
        this._viewModel.moveMarker("context", this.makeProjectRelativeIfPossible(currentContext),
                                   this.makeProjectRelativeIfPossible(path));
    };

    /**
     * Restores the context to the last non-null context. This is specifically here to handle
     * the sequence of messages that we get from the project context menu.
     */
    ProjectModel.prototype.restoreContext = function () {
        if (this._selections.previousContext) {
            this.setContext(this._selections.previousContext);
        }
    };

    /**
     * Gets the currently selected context.
     *
     * @return {FileSystemEntry} filesystem object for the context file or directory
     */
    ProjectModel.prototype.getContext = function () {
        return _getFSObject(this._selections.context);
    };

    /**
     * Starts a rename operation for the file or directory at the given path. If the path is
     * not provided, the current context is used.
     *
     * If a rename operation is underway, it will be completed automatically.
     *
     * The Promise returned is resolved with an object with a `newPath` property with the renamed path. If the user cancels the operation, the promise is resolved with the value RENAME_CANCELLED.
     *
     * @param {string=} path optional path to start renaming
     * @return {$.Promise} resolved when the operation is complete.
     */
    ProjectModel.prototype.startRename = function (path) {
        path = _getPathFromFSObject(path);
        if (!path) {
            path = this._selections.context;
            if (!path) {
                return new $.Deferred().resolve().promise();
            }
        }

        if (this._selections.rename && this._selections.rename.path === path) {
            return;
        }

        if (path !== this._selections.context) {
            this.setContext(path);
        } else {
            this.performRename();
        }

        this._viewModel.moveMarker("rename", null,
                                   this.makeProjectRelativeIfPossible(path));
        var d = new $.Deferred();
        this._selections.rename = {
            deferred: d,
            type: FILE_RENAMING,
            path: path,
            newName: FileUtils.getBaseName(path)
        };
        return d.promise();
    };

    /**
     * Sets the new value for the rename operation that is in progress (started previously with a call
     * to `startRename`).
     *
     * @param {string} name new name for the file or directory being renamed
     */
    ProjectModel.prototype.setRenameValue = function (name) {
        if (!this._selections.rename) {
            return;
        }
        this._selections.rename.newName = name;
    };

    /**
     * Cancels the rename operation that is in progress. This resolves the original promise with
     * a RENAME_CANCELLED value.
     */
    ProjectModel.prototype.cancelRename = function () {
        var renameInfo = this._selections.rename;
        if (!renameInfo) {
            return;
        }

        // File creation is a special case.
        if (renameInfo.type === FILE_CREATING) {
            this._cancelCreating();
            return;
        }

        this._viewModel.moveMarker("rename", this.makeProjectRelativeIfPossible(renameInfo.path), null);
        renameInfo.deferred.resolve(RENAME_CANCELLED);
        delete this._selections.rename;
        this.setContext(null);
    };

    /**
     * Rename a file/folder. This will update the project tree data structures
     * and send notifications about the rename.
     *
     * @param {string} oldName Old item name
     * @param {string} newName New item name
     * @param {boolean} isFolder True if item is a folder; False if it is a file.
     * @return {$.Promise} A promise object that will be resolved or rejected when
     *   the rename is finished.
     */
    function _renameItem(oldName, newName, isFolder) {
        var result = new $.Deferred();

        if (oldName === newName) {
            result.resolve();
        } else if (!isValidFilename(FileUtils.getBaseName(newName), _invalidChars)) {
            result.reject(ERROR_INVALID_FILENAME);
        } else {
            var entry = isFolder ? FileSystem.getDirectoryForPath(oldName) : FileSystem.getFileForPath(oldName);
            entry.rename(newName, function (err) {
                if (err) {
                    result.reject(err);
                } else {
                    result.resolve();
                }
            });
        }

        return result.promise();
    }

    /**
     * @private
     *
     * Renames the item at the old path to the new name provided.
     *
     * @param {string} oldPath full path to the current location of file or directory (should include trailing slash for directory)
     * @param {string} newName new name for the file or directory
     */
    ProjectModel.prototype._renameItem = function (oldPath, newName) {
        return _renameItem(oldPath, newName, !_pathIsFile(oldPath));
    };

    /**
     * Completes the rename operation that is in progress.
     */
    ProjectModel.prototype.performRename = function () {
        var renameInfo = this._selections.rename;
        if (!renameInfo) {
            return;
        }
        var oldPath         = renameInfo.path,
            isFolder        = renameInfo.isFolder || !_pathIsFile(oldPath),
            oldProjectPath  = this.makeProjectRelativeIfPossible(oldPath),

            // To get the parent directory, we need to strip off the trailing slash on a directory name
            parentDirectory = FileUtils.getDirectoryPath(isFolder ? FileUtils.stripTrailingSlash(oldPath) : oldPath),
            oldName         = FileUtils.getBaseName(oldPath),
            newName         = renameInfo.newName,
            newPath         = parentDirectory + newName,
            viewModel       = this._viewModel,
            self            = this;

        if (oldName === newName) {
            this.cancelRename();
            return;
        }

        if (isFolder) {
            newPath += "/";
        }

        delete this._selections.rename;
        delete this._selections.context;
        viewModel.moveMarker("rename", oldProjectPath, null);
        viewModel.moveMarker("context", oldProjectPath, null);
        viewModel.moveMarker("creating", oldProjectPath, null);

        if (renameInfo.type === FILE_CREATING) {
            this.createAtPath(newPath).done(function (entry) {
                viewModel.renameItem(oldProjectPath, newName);
                renameInfo.deferred.resolve(entry);
            }).fail(function (error) {
                self._cancelCreating();
                renameInfo.deferred.reject(error);
            });
        } else {
            this._renameItem(oldPath, newPath).then(function () {
                viewModel.renameItem(oldProjectPath, newName);
                renameInfo.deferred.resolve({
                    newPath: newPath
                });
            }).fail(function (error) {
                renameInfo.deferred.reject(error);
            });
        }
    };

    /**
     * Creates a file or folder at the given path. Folder paths should have a trailing slash.
     *
     * If an error comes up during creation, the ERROR_CREATION event is triggered.
     *
     * @param {string} path full path to file or folder to create
     * @return {$.Promise} resolved when creation is complete
     */
    ProjectModel.prototype.createAtPath = function (path) {
        var isFolder  = !_pathIsFile(path),
            name      = FileUtils.getBaseName(path),
            self      = this;

        return doCreate(path, isFolder).done(function (entry) {
            if (!isFolder) {
                self.setSelected(entry.fullPath);
            }
        }).fail(function (error) {
            $(self).trigger(ERROR_CREATION, {
                type: error,
                name: name,
                isFolder: isFolder
            });
        });
    };

    /**
     * Starts creating a file or folder with the given name in the given directory.
     *
     * The Promise returned is resolved with an object with a `newPath` property with the renamed path. If the user cancels the operation, the promise is resolved with the value RENAME_CANCELLED.
     *
     * @param {string} basedir directory that should contain the new entry
     * @param {string} newName initial name for the new entry (the user can rename it)
     * @param {boolean} isFolder `true` if the entry being created is a folder
     * @return {$.Promise} resolved when the user is done creating the entry.
     */
    ProjectModel.prototype.startCreating = function (basedir, newName, isFolder) {
        this.performRename();
        var d = new $.Deferred(),
            self = this;

        this.setDirectoryOpen(basedir, true).then(function () {
            self._viewModel.createPlaceholder(self.makeProjectRelativeIfPossible(basedir), newName, isFolder);
            var promise = self.startRename(basedir + newName);
            self._selections.rename.type = FILE_CREATING;
            if (isFolder) {
                self._selections.rename.isFolder = isFolder;
            }
            promise.then(d.resolve).fail(d.reject);
        }).fail(function (err) {
            d.reject(err);
        });
        return d.promise();
    };

    /**
     * Cancels the creation process that is underway. The original promise returned will be resolved with the
     * RENAME_CANCELLED value. The temporary entry added to the file tree will be deleted.
     */
    ProjectModel.prototype._cancelCreating = function () {
        var renameInfo = this._selections.rename;
        if (!renameInfo || renameInfo.type !== FILE_CREATING) {
            return;
        }
        this._viewModel.deleteAtPath(this.makeProjectRelativeIfPossible(renameInfo.path));
        renameInfo.deferred.resolve(RENAME_CANCELLED);
        delete this._selections.rename;
        this.setContext(null);
    };

    /**
     * Sets the `sortDirectoriesFirst` option for the file tree view.
     *
     * @param {boolean} True if directories should appear first
     */
    ProjectModel.prototype.setSortDirectoriesFirst = function (sortDirectoriesFirst) {
        this._viewModel.setSortDirectoriesFirst(sortDirectoriesFirst);
    };

    /**
     * Gets an array of arrays where each entry of the top-level array has an array
     * of paths that are at the same depth in the tree. All of the paths are full paths.
     *
     * @return {Array.<Array.<string>>} Array of array of full paths, organized by depth in the tree.
     */
    ProjectModel.prototype.getOpenNodes = function () {
        return this._viewModel.getOpenNodes(this.projectRoot.fullPath);
    };

    /**
     * Reopens a set of nodes in the tree by full path.
     * @param {Array.<Array.<string>>} nodesByDepth An array of arrays of node ids to reopen. The ids within
     *     each sub-array are reopened in parallel, and the sub-arrays are reopened in order, so they should
     *     be sorted by depth within the tree.
     * @return {$.Deferred} A promise that will be resolved when all nodes have been fully
     *     reopened.
     */
    ProjectModel.prototype.reopenNodes = function (nodesByDepth) {
        var deferred = new $.Deferred();

        if (!nodesByDepth || nodesByDepth.length === 0) {
            // All paths are opened and fully rendered.
            return deferred.resolve().promise();
        } else {
            var self = this;
            return Async.doSequentially(nodesByDepth, function (toOpenPaths) {
                return Async.doInParallel(
                    toOpenPaths,
                    function (path) {
                        return self._getDirectoryContents(path).then(function (contents) {
                            var relative = self.makeProjectRelativeIfPossible(path);
                            self._viewModel.setDirectoryContents(relative, contents);
                            self._viewModel.setDirectoryOpen(relative, true);
                        });
                    },
                    false
                );
            });
        }
    };

    /**
     * Refreshes the contents of the tree.
     *
     * @return {$.Promise} resolved when the tree has been refreshed
     */
    ProjectModel.prototype.refresh = function () {
        var projectRoot = this.projectRoot,
            openNodes   = this.getOpenNodes(),
            self        = this,
            selections  = this._selections,
            viewModel   = this._viewModel,
            deferred    = new $.Deferred();

        this.setProjectRoot(projectRoot).then(function () {
            self.reopenNodes(openNodes).then(function () {
                if (selections.selected) {
                    viewModel.moveMarker("selected", null, self.makeProjectRelativeIfPossible(selections.selected));
                }

                if (selections.context) {
                    viewModel.moveMarker("context", null, self.makeProjectRelativeIfPossible(selections.context));
                }

                if (selections.rename) {
                    viewModel.moveMarker("rename", null, self.makeProjectRelativeIfPossible(selections.rename));
                }

                deferred.resolve();
            });
        });

        return deferred.promise();
    };

    /**
     * Handles filesystem change events and prepares the update for the view model.
     *
     * @param {?(File|Directory)} entry File or Directory changed
     * @param {Array.<FileSystemEntry>=} added If entry is a Directory, contains zero or more added children
     * @param {Array.<FileSystemEntry>=} removed If entry is a Directory, contains zero or more removed
     */
    ProjectModel.prototype.handleFSEvent = function (entry, added, removed) {
        this._resetCache();

        if (!entry) {
            this.refresh();
            return;
        }

        if (!this.isWithinProject(entry)) {
            return;
        }

        var changes = {},
            self = this;

        if (entry.isFile) {
            changes.changed = [
                this.makeProjectRelativeIfPossible(entry.fullPath)
            ];
        }

        if (added) {
            changes.added = added.map(function (entry) {
                return self.makeProjectRelativeIfPossible(entry.fullPath);
            });
        }

        if (removed) {
            changes.removed = removed.map(function (entry) {
                return self.makeProjectRelativeIfPossible(entry.fullPath);
            });
        }

        this._viewModel.processChanges(changes);
    };

    /**
     * Closes the directory at path and recursively closes all of its children.
     *
     * @param {string} path Path of subtree to close
     */
    ProjectModel.prototype.closeSubtree = function (path) {
        this._viewModel.closeSubtree(this.makeProjectRelativeIfPossible(path));
    };

    /**
     * Toggle the open state of subdirectories.
     * @param {!string}  path        parent directory
     * @param {boolean} openOrClose  true to open directory, false to close
     * @return {$.Promise} promise resolved when the directories are open
     */
    ProjectModel.prototype.toggleSubdirectories = function (path, openOrClose) {
        var self = this,
            d = new $.Deferred();

        this.setDirectoryOpen(path, true).then(function () {
            var projectRelativePath = self.makeProjectRelativeIfPossible(path),
                childNodes = self._viewModel.getChildDirectories(projectRelativePath);

            Async.doInParallel(childNodes, function (node) {
                return self.setDirectoryOpen(path + node, openOrClose);
            }, true).then(function () {
                d.resolve();
            }, function (err) {
                d.reject(err);
            });
        });

        return d.promise();
    };

    /**
     * Although Brackets is generally standardized on folder paths with a trailing "/", some APIs here
     * receive project paths without "/" due to legacy preference storage formats, etc.
     * @param {!string} fullPath  Path that may or may not end in "/"
     * @return {!string} Path that ends in "/"
     */
    function _ensureTrailingSlash(fullPath) {
        if (_pathIsFile(fullPath)) {
            return fullPath + "/";
        }
        return fullPath;
    }

    /**
     * @private
     *
     * Returns the full path to the welcome project, which we open on first launch.
     *
     * @param {string} sampleUrl URL for getting started project
     * @param {string} initialPath Path to Brackets directory (see FileUtils.getNativeBracketsDirectoryPath())
     * @return {!string} fullPath reference
     */
    function _getWelcomeProjectPath(sampleUrl, initialPath) {
        if (sampleUrl) {
            // Back up one more folder. The samples folder is assumed to be at the same level as
            // the src folder, and the sampleUrl is relative to the samples folder.
            initialPath = initialPath.substr(0, initialPath.lastIndexOf("/")) + "/samples/" + sampleUrl;
        }

        return _ensureTrailingSlash(initialPath); // paths above weren't canonical
    }

    /**
     * @private
     *
     * Adds the path to the list of welcome projects we've ever seen, if not on the list already.
     *
     * @param {string} path Path to possibly add
     * @param {Array.<string>=} currentProjects Array of current welcome projects
     * @return {Array.<string>} New array of welcome projects with the additional project added
     */
    function _addWelcomeProjectPath(path, currentProjects) {
        var pathNoSlash = FileUtils.stripTrailingSlash(path);  // "welcomeProjects" pref has standardized on no trailing "/"

        var newProjects;

        if (currentProjects) {
            newProjects = _.clone(currentProjects);
        } else {
            newProjects = [];
        }

        if (newProjects.indexOf(pathNoSlash) === -1) {
            newProjects.push(pathNoSlash);
        }
        return newProjects;
    }

    /**
     * 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.
     *
     * @param {string} path Path to check to see if it's a welcome project
     * @param {string} welcomeProjectPath Current welcome project path
     * @param {Array.<string>=} welcomeProjects All known welcome projects
     */
    function _isWelcomeProjectPath(path, welcomeProjectPath, welcomeProjects) {
        if (path === welcomeProjectPath) {
            return true;
        }

        // No match on the current path, and it's not a match if there are no previously known projects
        if (!welcomeProjects) {
            return false;
        }

        var pathNoSlash = FileUtils.stripTrailingSlash(path);  // "welcomeProjects" pref has standardized on no trailing "/"
        return welcomeProjects.indexOf(pathNoSlash) !== -1;
    }

    // Init invalid characters string
    if (brackets.platform === "mac") {
        _invalidChars = "?*|:";
    } else if (brackets.platform === "linux") {
        _invalidChars = "?*|/";
    } else {
        _invalidChars = "/?*:<>\\|\"";  // invalid characters on Windows
    }

    exports._getWelcomeProjectPath  = _getWelcomeProjectPath;
    exports._addWelcomeProjectPath  = _addWelcomeProjectPath;
    exports._isWelcomeProjectPath   = _isWelcomeProjectPath;
    exports._ensureTrailingSlash    = _ensureTrailingSlash;
    exports._shouldShowName         = _shouldShowName;
    exports._invalidChars           = _invalidChars;

    exports.shouldShow              = shouldShow;
    exports.isValidFilename         = isValidFilename;
    exports.EVENT_CHANGE            = EVENT_CHANGE;
    exports.EVENT_SHOULD_SELECT     = EVENT_SHOULD_SELECT;
    exports.EVENT_SHOULD_FOCUS      = EVENT_SHOULD_FOCUS;
    exports.ERROR_CREATION          = ERROR_CREATION;
    exports.ERROR_INVALID_FILENAME  = ERROR_INVALID_FILENAME;
    exports.FILE_RENAMING           = FILE_RENAMING;
    exports.FILE_CREATING           = FILE_CREATING;
    exports.RENAME_CANCELLED        = RENAME_CANCELLED;
    exports.doCreate                = doCreate;
    exports.ProjectModel            = ProjectModel;
});


/* Desktop */
@import url("desktop.css");
/* Phone */
@import url("phone.css") only screen and (max-width:320px);
/* Tablet */
@import url("tablet.css") only screen and (min-width:321px) and (max-width:768px);


(function( $ ) {

window.TestHelpers = {};

function includeStyle( url ) {
	document.write( "<link rel='stylesheet' href='../../../" + url + "'>" );
}

function includeScript( url ) {
	document.write( "<script src='../../../" + url + "'></script>" );
}

QUnit.config.urlConfig.push( "min" );
TestHelpers.loadResources = QUnit.urlParams.min ?
	function() {
		// TODO: proper include with theme images
		includeStyle( "dist/jquery-ui.min.css" );
		includeScript( "dist/jquery-ui.min.js" );
	} :
	function( resources ) {
		$.each( resources.css || [], function( i, resource ) {
			includeStyle( "themes/base/jquery." + resource + ".css" );
		});
		$.each( resources.js || [], function( i, resource ) {
			includeScript( resource );
		});
	};

QUnit.config.urlConfig.push( "nojshint" );
var jshintLoaded = false;
TestHelpers.testJshint = function( module ) {
	if ( QUnit.urlParams.nojshint ) {
		return;
	}

	if ( !jshintLoaded ) {
		includeScript( "external/jshint.js" );
		jshintLoaded = true;
	}

	asyncTest( "JSHint", function() {
		expect( 1 );

		$.when(
			$.ajax({
				url: "../../../ui/.jshintrc",
				dataType: "json"
			}),
			$.ajax({
				url: "../../../ui/jquery." + module + ".js",
				dataType: "text"
			})
		).done(function( hintArgs, srcArgs ) {
			var passed = JSHINT( srcArgs[ 0 ], hintArgs[ 0 ] ),
				errors = $.map( JSHINT.errors, function( error ) {
					// JSHINT may report null if there are too many errors
					if ( !error ) {
						return;
					}

					return "[L" + error.line + ":C" + error.character + "] " +
						error.reason + "\n" + error.evidence + "\n";
				}).join( "\n" );
			ok( passed, errors );
			start();
		})
		.fail(function() {
			ok( false, "error loading source" );
			start();
		});
	});
};

function testWidgetDefaults( widget, defaults ) {
	var pluginDefaults = $.ui[ widget ].prototype.options;

	// ensure that all defaults have the correct value
	test( "defined defaults", function() {
		$.each( defaults, function( key, val ) {
			if ( $.isFunction( val ) ) {
				ok( $.isFunction( pluginDefaults[ key ] ), key );
				return;
			}
			deepEqual( pluginDefaults[ key ], val, key );
		});
	});

	// ensure that all defaults were tested
	test( "tested defaults", function() {
		$.each( pluginDefaults, function( key, val ) {
			ok( key in defaults, key );
		});
	});
}

function testWidgetOverrides( widget ) {
	if ( $.uiBackCompat === false ) {
		test( "$.widget overrides", function() {
			$.each([
				"_createWidget",
				"destroy",
				"option",
				"_trigger"
			], function( i, method ) {
				strictEqual( $.ui[ widget ].prototype[ method ],
					$.Widget.prototype[ method ], "should not override " + method );
			});
		});
	}
}

function testBasicUsage( widget ) {
	test( "basic usage", function() {
		var defaultElement = $.ui[ widget ].prototype.defaultElement;
		$( defaultElement ).appendTo( "body" )[ widget ]().remove();
		ok( true, "initialized on element" );

		$( defaultElement )[ widget ]().remove();
		ok( true, "initialized on disconnected DOMElement - never connected" );

		$( defaultElement ).appendTo( "body" ).remove()[ widget ]().remove();
		ok( true, "initialized on disconnected DOMElement - removed" );
	});
}

TestHelpers.commonWidgetTests = function( widget, settings ) {
	module( widget + ": common widget" );

	TestHelpers.testJshint( "ui." + widget );
	testWidgetDefaults( widget, settings.defaults );
	testWidgetOverrides( widget );
	testBasicUsage( widget );
	test( "version", function() {
		ok( "version" in $.ui[ widget ].prototype, "version property exists" );
	});
};

/*
 * Experimental assertion for comparing DOM objects.
 *
 * Serializes an element and some properties and attributes and it's children if any, otherwise the text.
 * Then compares the result using deepEqual.
 */
window.domEqual = function( selector, modifier, message ) {
	var expected, actual,
		properties = [
			"disabled",
			"readOnly"
		],
		attributes = [
			"autocomplete",
			"aria-activedescendant",
			"aria-controls",
			"aria-describedby",
			"aria-disabled",
			"aria-expanded",
			"aria-haspopup",
			"aria-hidden",
			"aria-labelledby",
			"aria-pressed",
			"aria-selected",
			"aria-valuemax",
			"aria-valuemin",
			"aria-valuenow",
			"class",
			"href",
			"id",
			"nodeName",
			"role",
			"tabIndex",
			"title"
		];

	function extract( elem ) {
		if ( !elem || !elem.length ) {
			QUnit.push( false, actual, expected,
				"domEqual failed, can't extract " + selector + ", message was: " + message );
			return;
		}

		var children,
			result = {};
		$.each( properties, function( index, attr ) {
			var value = elem.prop( attr );
			result[ attr ] = value !== undefined ? value : "";
		});
		$.each( attributes, function( index, attr ) {
			var value = elem.attr( attr );
			result[ attr ] = value !== undefined ? value : "";
		});
		result.events = $._data( elem[ 0 ], "events" );
		result.data = $.extend( {}, elem.data() );
		delete result.data[ $.expando ];
		children = elem.children();
		if ( children.length ) {
			result.children = elem.children().map(function( ind ) {
				return extract( $( this ) );
			}).get();
		} else {
			result.text = elem.text();
		}
		return result;
	}
	expected = extract( $( selector ) );
	modifier( $( selector ) );

	actual = extract( $( selector ) );
	QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
};

}( jQuery ));


/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */
/*global define, $ */

/**
 * DOMNode represents a node in the DOM tree. It is constructed from a payload
 * similar to {DOM.Node} and supports all basic tree operations. If a node has
 * a nodeId it is registered with the `DOMAgent` via `addNode()`. The node's
 * sourceOffset and sourceLength is stored as its location and length. Nodes can
 * iterated using `each()` or `find()`. `dump` shows the entire tree on the console.
 */
define(function DOMNodeModule(require, exports, module) {
    "use strict";

    var DOMHelpers = require("LiveDevelopment/Agents/DOMHelpers");

    /** Fill a string to the given length (used for debug output)
     * @param {string} source string
     * @param {integer} length
     * @param {char} fill character
     */
    function _fill(string, length, c) {
        if (c === undefined) {
            c = " ";
        }
        while (string.length < length) {
            string += c;
        }
        return string;
    }

    /** Construct a find condition (used in `find` and `findParent`)
     * The match can be a callback returning true or false, the node
     * name or the node type.
     * @param {function} or {string} or {number} match criteria
     */
    function _makeFindCondition(match) {
        switch (typeof match) {
        case "function":
            return match;
        case "string":
            return function findCondition(name, node) {
                return node.name === name;
            }.bind(undefined, match.toUpperCase());
        case "number":
            return function findCondition(type, node) {
                return node.type === type;
            }.bind(undefined, match);
        default:
            console.error("Invalid find condition: " + match);
        }
    }

    /** Constructor
     * @param {DOMAgent} the agent is passed to avoid circular relationships
     * @param {DOM.Node} node payload
     */
    var DOMNode = function DOMNode(agent, payload) {
        this.agent = agent;
        this.children = [];
        this.attributes = {};

        // set the payload
        if (typeof payload === "string") {
            payload = DOMHelpers.extractPayload(payload);
        }
        if (payload) {
            this.setPayload(payload);
        }
        this.agent.addNode(this);
    };

    var TYPE_ELEMENT = DOMNode.TYPE_ELEMENT = 1; // element node
    var TYPE_ATTRIBUTE = DOMNode.TYPE_ATTRIBUTE = 2; // attribute node (unused)
    var TYPE_TEXT = DOMNode.TYPE_TEXT = 3; // text node
    var TYPE_COMMENT = DOMNode.TYPE_COMMENT = 8; // comment node <!-- -->
    var TYPE_DOCUMENT = DOMNode.TYPE_DOCUMENT = 9; // document node <!DOCUMENT>

    /** Remove a node */
    DOMNode.prototype.remove = function remove() {
        this.agent.removeNode(this);
        if (this.parent) {
            this.parent.removeChild(this);
        }
    };

    /** Node Payload ***********************************************************/

    /** Set the node payload
     * @param {DOM.Node} payload
     */
    DOMNode.prototype.setPayload = function setPayload(payload) {
        this.nodeId = payload.nodeId;
        this.type = payload.nodeType;
        if (payload.nodeName) {
            this.name = payload.nodeName;
        }
        if (payload.nodeValue) {
            this.value = payload.nodeValue;
        }
        this.attributes = {};
        if (payload.attributes) {
            var i, k, v;
            for (i = 0; i < payload.attributes.length; i += 2) {
                k = payload.attributes[i];
                v = payload.attributes[i + 1];
                this.attributes[k] = v;
            }
        }
        if (payload.sourceOffset) {
            this.location = payload.sourceOffset;
        }
        if (payload.sourceLength) {
            this.length = payload.sourceLength;
        } else {
            if (this.value) {
                this.length = this.value.length;
            } else if (this.name) {
                this.length = this.name.length + 2;
            }
        }
        if (payload.children) {
            this.setChildrenPayload(payload.children);
        } else if (payload.childNodeCount) {
            this.agent.requestChildNodes(this);
        }
    };

    /** Create child nodes from the given payload
     * @param [{DOM.Node}] payload of the children
     */
    DOMNode.prototype.setChildrenPayload = function setChildrenPayload(childrenPayload) {
        var i, payload, node;
        for (i in childrenPayload) {
            payload = childrenPayload[i];
            node = new DOMNode(this.agent, payload);
            this.appendChild(node);
        }
    };

    /** Construct the payload for this node */
    DOMNode.prototype.payload = function payload() {
        var res = { type: this.type };
        if (this.nodeType === TYPE_ELEMENT) {
            res.nodeName = this.name;
        } else {
            res.value = this.value;
        }
        return res;
    };

    /** Find the next node that matches the given payload
     * @param {DOM.Node} payload
     */
    DOMNode.prototype.findParentForNextNodeMatchingPayload = function findParentForNextNodeMatchingPayload(payload) {
        var parent = this.canHaveChildren() ? this : this.parent;
        while (parent && !parent.matchesPayload(payload)) {
            parent = parent.parent;
        }
        return parent;
    };

    /** Find the next node that matches the given payload
     * @param {DOM.Node} payload
     */
    DOMNode.prototype.findNextNodeMatchingPayload = function findNextNodeMatchingPayload(payload) {
        var next = this.nextNode();
        while (next && !next.matchesPayload(payload)) {
            next = next.nextNode();
        }
        return next;
    };

    /** Test if the node matches the given payload
     * @param {DOM.Node} payload
     */
    DOMNode.prototype.matchesPayload = function matchesPayload(payload) {
        var r = false;
        if (this.type === payload.nodeType) {
            switch (this.type) {
            case 1:
                r = this.name === payload.nodeName;
                break;
            case 3:
                // TODO payload.nodeValue's HTML Entities must be decoded
                // r = this.value === payload.nodeValue;
                r = true;
                break;
            default:
                r = true;
            }
        }
        // Useful output for debugging this - do not remove
        // console.debug(this.type + "," + this.name + "," + this.value + " = " + payload.nodeType + "," + payload.nodeName + "," + payload.value + " -> " + r);
        return r;
    };

    /** Resolve the node and retrieve its objectId from the remote debugger */
    DOMNode.prototype.resolve = function resolve() {
        var def = new $.Deferred();
        if (this.objectId) {
            def.resolve(this);
        } else if (!this.nodeId) {
            def.reject();
        } else {
            this.agent.resolveNode(this, function onResolve(res) {
                this.objectId = res.object.objectId;
                def.resolve(this);
            }.bind(this));
        }
        return def.promise();
    };

    /** Tree Operations ******************************************************/

    /** Can the node have children? */
    DOMNode.prototype.canHaveChildren = function canHaveChildren() {
        return (this.type === 1 && !this.closed && !this.closing && this.nodeName !== "LINK");
    };

    /** Remove a child
     * @param {DOMNode} child node to remove
     */
    DOMNode.prototype.removeChild = function removeChild(node) {
        this.children.splice(this.indexOfChild(node), 1);
        delete node.parent;
    };

    /** Insert a child node at the given index
     * @param {DOMNode} node to insert
     * @param {integer} optional index (node is appended if missing)
     */
    DOMNode.prototype.insertChildAt = function insertChildAt(node, index) {
        if (node.parent) {
            node.parent.removeChild(node);
        }
        if (!index || index < 0 || index > this.children.length) {
            index = this.children.length;
        }
        this.children.splice(index, 0, node);
        node.parent = this;
        return node;
    };

    /** Append a child to this node
     * @param {DOMNode} child node to append
     */
    DOMNode.prototype.appendChild = function appendChild(node) {
        return this.insertChildAt(node);
    };

    /** Insert a child node after the given node
     * @param {DOMNode} child node to insert
     * @param {DOMNode} existing child node
     */
    DOMNode.prototype.insertChildAfter = function insertChildAfter(node, sibling) {
        var index = this.indexOfChild(sibling);
        if (index >= 0) {
            index++;
        }
        return this.insertChildAt(node, index);
    };

    /** Insert a child node before the given node
     * @param {DOMNode} child node to insert
     * @param {DOMNode} existing child node
     */
    DOMNode.prototype.insertChildBefore = function insertChildBefore(node, sibling) {
        var index = this.indexOfChild(sibling);
        return this.insertChildAt(node, index);
    };

    /** Determine the index of a child node
     * @param {DOMNode} child node
     */
    DOMNode.prototype.indexOfChild = function indexOfChild(node) {
        if (!node) {
            return -1;
        }
        var i;
        for (i in this.children) {
            if (this.children[i] === node) {
                return parseInt(i, 0);
            }
        }
        return -1;
    };

    /** Get the previous sibling */
    DOMNode.prototype.previousSibling = function previousSibling() {
        if (!this.parent) {
            return null;
        }
        return this.parent.children[this.parent.indexOfChild(this) - 1];
    };

    /** Get the next sibling */
    DOMNode.prototype.nextSibling = function nextSibling() {
        if (!this.parent) {
            return null;
        }
        return this.parent.children[this.parent.indexOfChild(this) + 1];
    };

    /** Get the previous node */
    DOMNode.prototype.previousNode = function previousNode() {
        var node = this.previousSibling();
        if (node) {
            if (node.children.length > 0) {
                node = node.children[node.children.length - 1];
            }
        } else {
            node = this.parent;
        }
        return node;
    };

    /** Get the next node */
    DOMNode.prototype.nextNode = function nextNode() {
        if (this.children.length > 0) {
            // return the first child
            return this.children[0];
        }
        // return this or any ancestor's next sibling
        var node, parent = this;
        while (parent) {
            node = parent.nextSibling();
            if (node) {
                return node;
            }
            parent = parent.parent;
        }
        return null;
    };

    /** Traverse the tree
     * @param {function({DOM.Node})} called for this node and all descendants
     */
    DOMNode.prototype.each = function each(callback) {
        if (callback(this) === false) {
            return false;
        }
        var i;
        for (i in this.children) {
            if (this.children[i].each(callback) === false) {
                return false;
            }
        }
        return true;
    };

    /** Find a node in the tree
     * @param {function} or {string} or {integer} find condition
     */
    DOMNode.prototype.find = function find(match) {
        var findCondition = _makeFindCondition(match);
        var node = null;
        this.each(function each(n) {
            if (findCondition(n)) {
                node = n;
                return false;
            }
        });
        return node;
    };

    /** Find all nodes with the given find condition
     * @param {function} or {string} or {integer} find condition
     */
    DOMNode.prototype.findAll = function findAll(match) {
        var nodes = [];
        var findCondition = _makeFindCondition(match);
        this.each(function each(node) {
            if (findCondition(node)) {
                nodes.push(node);
            }
        });
        return nodes;
    };

    /** Iterate over all parent nodes
     * @param {function({DOM.Node})} called for each ancestor
     */
    DOMNode.prototype.eachParent = function eachParent(callback) {
        var node = this.parent;
        while (node) {
            if (callback(node) === false) {
                return;
            }
            node = node.parent;
        }
        return null;
    };

    /** Find a parent node that matches the find condition
     * @param {function} or {string} or {integer} find condition
     */
    DOMNode.prototype.findParent = function findParent(findCondition) {
        var theParent = null;
        this.eachParent(function each(parent) {
            if (findCondition(parent)) {
                theParent = parent;
                return false;
            }
        });
        return theParent;
    };

    /** Find the root of the tree */
    DOMNode.prototype.root = function root() {
        var node = this;
        while (node.parent) {
            node = node.parent;
        }
        return node;
    };

   /** Node Info ***********************************************************/

   /** Test if the given location is inside this node
    * @param {integer} location
    * @param {boolean} also include children
    */
    DOMNode.prototype.isAtLocation = function isAtLocation(location, includeChildren) {
        if (includeChildren === undefined) {
            includeChildren = true;
        }
        if (!this.location || location < this.location) {
            return false;
        }
        var to;
        if (includeChildren && this.closeLocation) {
            to = this.closeLocation + this.closeLength;
        } else {
            to = this.location + this.length;
        }
        if (this.type === TYPE_TEXT) {
            to += 1;
        }
        return location < to;
    };

    /** Test if this node is empty */
    DOMNode.prototype.isEmpty = function isEmpty() {
        return this.type === TYPE_TEXT && /^\s*$/.test(this.value);
    };

    /** Debug Output */
    DOMNode.prototype.toString = function toString() {
        var r;
        switch (this.type) {
        case TYPE_ELEMENT:
            r = "<" + this.name + ">";
            break;
        case TYPE_ATTRIBUTE:
            r = "[ATTRIBUTE]";
            break;
        case TYPE_TEXT:
            r = this.value.replace(/\s+/, " ").substr(0, 40);
            break;
        case TYPE_COMMENT:
            r = "<!--" + this.value.replace(/\s+/, " ").substr(0, 33) + "-->";
            break;
        case TYPE_DOCUMENT:
            r = "<!DOCTYPE>";
            break;
        }
        return r;
    };

    /** Detailed Debug Output */
    DOMNode.prototype.dump = function dump(pre) {
        if (pre === undefined) {
            pre = "";
        }
        var r = pre + this.toString();
        if (this.location) {
            r = _fill(r, 60);
            r += " (" + this.location + "," + (this.location + this.length) + ")";
            if (this.closeLocation) {
                r += " (" + this.closeLocation + "," + (this.closeLocation + this.closeLength) + ")";
            }
        }
        if (this.nodeId) {
            r = _fill(r, 80);
            r += " {" + this.nodeId + "}";
        }
        console.info(r);
        pre += ". ";
        var i;
        for (i in this.children) {
            this.children[i].dump(pre);
        }
    };

    return DOMNode;
});


* Sets the context (for context menu operations) to the given path. This is independent from the

* @param {string} path full path of file or directory to which the context should be setBaseUrl

* @param {boolean} _doNotRename True if this context change should not cause a rename operation to finish. This is a special case that goes with context menu handling.


// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

var common = require('../common.js');
var assert = require('assert');
var R = require('../../lib/_stream_readable');
var util = require('util');

// tiny node-tap lookalike.
var tests = [];
var count = 0;

function test(name, fn) {
  count++;
  tests.push([name, fn]);
}

function run() {
  var next = tests.shift();
  if (!next)
    return console.error('ok');

  var name = next[0];
  var fn = next[1];
  console.log('# %s', name);
  fn({
    same: assert.deepEqual,
    equal: assert.equal,
    end: function () {
      count--;
      run();
    }
  });
}

// ensure all tests have run
process.on("exit", function () {
  assert.equal(count, 0);
});

process.nextTick(run);

/////

util.inherits(TestReader, R);

function TestReader(n, opts) {
  R.call(this, opts);

  this.pos = 0;
  this.len = n || 100;
}

TestReader.prototype._read = function(n) {
  setTimeout(function() {

    if (this.pos >= this.len) {
      return this.push(null);
    }

    n = Math.min(n, this.len - this.pos);
    if (n <= 0) {
      return this.push(null);
    }

    this.pos += n;
    var ret = new Buffer(n);
    ret.fill('a');

    console.log("this.push(ret)", ret)

    return this.push(ret);
  }.bind(this), 1);
};

test('setEncoding utf8', function(t) {
  var tr = new TestReader(100);
  tr.setEncoding('utf8');
  var out = [];
  var expect =
    [ 'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa' ];

  tr.on('readable', function flow() {
    var chunk;
    while (null !== (chunk = tr.read(10)))
      out.push(chunk);
  });

  tr.on('end', function() {
    t.same(out, expect);
    t.end();
  });

  // just kick it off.
  tr.emit('readable');
});

test('setEncoding hex', function(t) {
  var tr = new TestReader(100);
  tr.setEncoding('hex');
  var out = [];
  var expect =
    [ '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161' ];

  tr.on('readable', function flow() {
    var chunk;
    while (null !== (chunk = tr.read(10)))
      out.push(chunk);
  });

  tr.on('end', function() {
    t.same(out, expect);
    t.end();
  });

  // just kick it off.
  tr.emit('readable');
});

test('setEncoding hex with read(13)', function(t) {
  var tr = new TestReader(100);
  tr.setEncoding('hex');
  var out = [];
  var expect =
    [ "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "16161" ];

  tr.on('readable', function flow() {
    console.log("readable once")
    var chunk;
    while (null !== (chunk = tr.read(13)))
      out.push(chunk);
  });

  tr.on('end', function() {
    console.log("END")
    t.same(out, expect);
    t.end();
  });

  // just kick it off.
  tr.emit('readable');
});

test('encoding: utf8', function(t) {
  var tr = new TestReader(100, { encoding: 'utf8' });
  var out = [];
  var expect =
    [ 'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa',
      'aaaaaaaaaa' ];

  tr.on('readable', function flow() {
    var chunk;
    while (null !== (chunk = tr.read(10)))
      out.push(chunk);
  });

  tr.on('end', function() {
    t.same(out, expect);
    t.end();
  });

  // just kick it off.
  tr.emit('readable');
});

test('encoding: hex', function(t) {
  var tr = new TestReader(100, { encoding: 'hex' });
  var out = [];
  var expect =
    [ '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161',
      '6161616161' ];

  tr.on('readable', function flow() {
    var chunk;
    while (null !== (chunk = tr.read(10)))
      out.push(chunk);
  });

  tr.on('end', function() {
    t.same(out, expect);
    t.end();
  });

  // just kick it off.
  tr.emit('readable');
});

test('encoding: hex with read(13)', function(t) {
  var tr = new TestReader(100, { encoding: 'hex' });
  var out = [];
  var expect =
    [ "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "1616161616161",
      "6161616161616",
      "16161" ];

  tr.on('readable', function flow() {
    var chunk;
    while (null !== (chunk = tr.read(13)))
      out.push(chunk);
  });

  tr.on('end', function() {
    t.same(out, expect);
    t.end();
  });

  // just kick it off.
  tr.emit('readable');
});


/*
 * dialog_events.js
 */
(function($) {

module("dialog: events");

test("open", function() {
	expect(13);

	el = $("

");
	el.dialog({
		open: function(ev, ui) {
			ok(el.data("dialog")._isOpen, "interal _isOpen flag is set");
			ok(true, 'autoOpen: true fires open callback');
			equal(this, el[0], "context of callback");
			equal(ev.type, 'dialogopen', 'event type in callback');
			deepEqual(ui, {}, 'ui hash in callback');
		}
	});
	el.remove();

	el = $("

");
	el.dialog({
		autoOpen: false,
		open: function(ev, ui) {
			ok(true, '.dialog("open") fires open callback');
			equal(this, el[0], "context of callback");
			equal(ev.type, 'dialogopen', 'event type in callback');
			deepEqual(ui, {}, 'ui hash in callback');
		}
	}).bind('dialogopen', function(ev, ui) {
		ok(el.data("dialog")._isOpen, "interal _isOpen flag is set");
		ok(true, 'dialog("open") fires open event');
		equal(this, el[0], 'context of event');
		deepEqual(ui, {}, 'ui hash in event');
	});
	el.dialog("open");
	el.remove();
});

test("dragStart", function() {
	expect(9);

	el = $('

').dialog({
		dragStart: function(ev, ui) {
			ok(true, 'dragging fires dragStart callback');
			equal(this, el[0], "context of callback");
			equal(ev.type, 'dialogdragstart', 'event type in callback');
			ok(ui.position !== undefined, "ui.position in callback");
			ok(ui.offset !== undefined, "ui.offset in callback");
		}
	}).bind('dialogdragstart', function(ev, ui) {
		ok(true, 'dragging fires dialogdragstart event');
		equal(this, el[0], 'context of event');
		ok(ui.position !== undefined, "ui.position in callback");
		ok(ui.offset !== undefined, "ui.offset in callback");
	});
	var handle = $(".ui-dialog-titlebar", dlg());
	drag(handle, 50, 50);
	el.remove();
});

test("drag", function() {
	expect(9);
	var handle,
		hasDragged = false;

	el = $('

').dialog({
		drag: function(ev, ui) {
			if (!hasDragged) {
				ok(true, 'dragging fires drag callback');
				equal(this, el[0], "context of callback");
				equal(ev.type, 'dialogdrag', 'event type in callback');
				ok(ui.position !== undefined, "ui.position in callback");
				ok(ui.offset !== undefined, "ui.offset in callback");

				hasDragged = true;
			}
		}
	}).one('dialogdrag', function(ev, ui) {
		ok(true, 'dragging fires dialogdrag event');
		equal(this, el[0], 'context of event');
		ok(ui.position !== undefined, "ui.position in callback");
		ok(ui.offset !== undefined, "ui.offset in callback");
	});
	handle = $(".ui-dialog-titlebar", dlg());
	drag(handle, 50, 50);
	el.remove();
});

test("dragStop", function() {
	expect(9);

	el = $('

').dialog({
		dragStop: function(ev, ui) {
			ok(true, 'dragging fires dragStop callback');
			equal(this, el[0], "context of callback");
			equal(ev.type, 'dialogdragstop', 'event type in callback');
			ok(ui.position !== undefined, "ui.position in callback");
			ok(ui.offset !== undefined, "ui.offset in callback");
		}
	}).bind('dialogdragstop', function(ev, ui) {
		ok(true, 'dragging fires dialogdragstop event');
		equal(this, el[0], 'context of event');
		ok(ui.position !== undefined, "ui.position in callback");
		ok(ui.offset !== undefined, "ui.offset in callback");
	});
	var handle = $(".ui-dialog-titlebar", dlg());
	drag(handle, 50, 50);
	el.remove();
});

test("resizeStart", function() {
	expect(13);

	el = $('

').dialog({
		resizeStart: function(ev, ui) {
			ok(true, 'resizing fires resizeStart callback');
			equal(this, el[0], "context of callback");
			equal(ev.type, 'dialogresizestart', 'event type in callback');
			ok(ui.originalPosition !== undefined, "ui.originalPosition in callback");
			ok(ui.originalSize !== undefined, "ui.originalSize in callback");
			ok(ui.position !== undefined, "ui.position in callback");
			ok(ui.size !== undefined, "ui.size in callback");
		}
	}).bind('dialogresizestart', function(ev, ui) {
		ok(true, 'resizing fires dialogresizestart event');
		equal(this, el[0], 'context of event');
		ok(ui.originalPosition !== undefined, "ui.originalPosition in callback");
		ok(ui.originalSize !== undefined, "ui.originalSize in callback");
		ok(ui.position !== undefined, "ui.position in callback");
		ok(ui.size !== undefined, "ui.size in callback");
	});
	var handle = $(".ui-resizable-se", dlg());
	drag(handle, 50, 50);
	el.remove();
});

test("resize", function() {
	expect(13);
	var handle,
		hasResized = false;

	el = $('

').dialog({
		resize: function(ev, ui) {
			if (!hasResized) {
				ok(true, 'resizing fires resize callback');
				equal(this, el[0], "context of callback");
				equal(ev.type, 'dialogresize', 'event type in callback');
				ok(ui.originalPosition !== undefined, "ui.originalPosition in callback");
				ok(ui.originalSize !== undefined, "ui.originalSize in callback");
				ok(ui.position !== undefined, "ui.position in callback");
				ok(ui.size !== undefined, "ui.size in callback");

				hasResized = true;
			}
		}
	}).one('dialogresize', function(ev, ui) {
		ok(true, 'resizing fires dialogresize event');
		equal(this, el[0], 'context of event');
		ok(ui.originalPosition !== undefined, "ui.originalPosition in callback");
		ok(ui.originalSize !== undefined, "ui.originalSize in callback");
		ok(ui.position !== undefined, "ui.position in callback");
		ok(ui.size !== undefined, "ui.size in callback");
	});
	handle = $(".ui-resizable-se", dlg());
	drag(handle, 50, 50);
	el.remove();
});

test("resizeStop", function() {
	expect(13);

	el = $('

').dialog({
		resizeStop: function(ev, ui) {
			ok(true, 'resizing fires resizeStop callback');
			equal(this, el[0], "context of callback");
			equal(ev.type, 'dialogresizestop', 'event type in callback');
			ok(ui.originalPosition !== undefined, "ui.originalPosition in callback");
			ok(ui.originalSize !== undefined, "ui.originalSize in callback");
			ok(ui.position !== undefined, "ui.position in callback");
			ok(ui.size !== undefined, "ui.size in callback");
		}
	}).bind('dialogresizestop', function(ev, ui) {
		ok(true, 'resizing fires dialogresizestop event');
		equal(this, el[0], 'context of event');
			ok(ui.originalPosition !== undefined, "ui.originalPosition in callback");
			ok(ui.originalSize !== undefined, "ui.originalSize in callback");
			ok(ui.position !== undefined, "ui.position in callback");
			ok(ui.size !== undefined, "ui.size in callback");
	});
	var handle = $(".ui-resizable-se", dlg());
	drag(handle, 50, 50);
	el.remove();
});

test("close", function() {
	expect(7);

	el = $('

').dialog({
		close: function(ev, ui) {
			ok(true, '.dialog("close") fires close callback');
			equal(this, el[0], "context of callback");
			equal(ev.type, 'dialogclose', 'event type in callback');
			deepEqual(ui, {}, 'ui hash in callback');
		}
	}).bind('dialogclose', function(ev, ui) {
		ok(true, '.dialog("close") fires dialogclose event');
		equal(this, el[0], 'context of event');
		deepEqual(ui, {}, 'ui hash in event');
	});
	el.dialog('close');
	el.remove();
});

test("beforeClose", function() {
	expect(14);

	el = $('

').dialog({
		beforeClose: function(ev, ui) {
			ok(true, '.dialog("close") fires beforeClose callback');
			equal(this, el[0], "context of callback");
			equal(ev.type, 'dialogbeforeclose', 'event type in callback');
			deepEqual(ui, {}, 'ui hash in callback');
			return false;
		}
	});
	el.dialog('close');
	isOpen('beforeClose callback should prevent dialog from closing');
	el.remove();

	el = $('

').dialog();
	el.dialog('option', 'beforeClose', function(ev, ui) {
		ok(true, '.dialog("close") fires beforeClose callback');
		equal(this, el[0], "context of callback");
		equal(ev.type, 'dialogbeforeclose', 'event type in callback');
		deepEqual(ui, {}, 'ui hash in callback');
		return false;
	});
	el.dialog('close');
	isOpen('beforeClose callback should prevent dialog from closing');
	el.remove();

	el = $('

').dialog().bind('dialogbeforeclose', function(ev, ui) {
		ok(true, '.dialog("close") triggers dialogbeforeclose event');
		equal(this, el[0], "context of event");
		deepEqual(ui, {}, 'ui hash in event');
		return false;
	});
	el.dialog('close');
	isOpen('dialogbeforeclose event should prevent dialog from closing');
	el.remove();
});

})(jQuery);


module.exports = rimraf
rimraf.sync = rimrafSync

var path = require("path")
  , fs

try {
  // optional dependency
  fs = require("graceful-fs")
} catch (er) {
  fs = require("fs")
}

// for EMFILE handling
var timeout = 0
exports.EMFILE_MAX = 1000
exports.BUSYTRIES_MAX = 3

function rimraf (p, cb) {
  if (!cb) throw new Error("No callback passed to rimraf()")

  var busyTries = 0
  rimraf_(p, function CB (er) {
    if (er) {
      if (er.code === "EBUSY" && busyTries < exports.BUSYTRIES_MAX) {
        busyTries ++
        var time = busyTries * 100
        // try again, with the same exact callback as this one.
        return setTimeout(function () {
          rimraf_(p, CB)
        }, time)
      }

      // this one won't happen if graceful-fs is used.
      if (er.code === "EMFILE" && timeout < exports.EMFILE_MAX) {
        return setTimeout(function () {
          rimraf_(p, CB)
        }, timeout ++)
      }

      // already gone
      if (er.code === "ENOENT") er = null
    }

    timeout = 0
    cb(er)
  })
}

// Two possible strategies.
// 1. Assume it's a file.  unlink it, then do the dir stuff on EPERM or EISDIR
// 2. Assume it's a directory.  readdir, then do the file stuff on ENOTDIR
//
// Both result in an extra syscall when you guess wrong.  However, there
// are likely far more normal files in the world than directories.  This
// is based on the assumption that a the average number of files per
// directory is >= 1.
//
// If anyone ever complains about this, then I guess the strategy could
// be made configurable somehow.  But until then, YAGNI.
function rimraf_ (p, cb) {
  fs.unlink(p, function (er) {
    if (er && er.code === "ENOENT")
      return cb()
    if (er && (er.code === "EPERM" || er.code === "EISDIR"))
      return rmdir(p, er, cb)
    return cb(er)
  })
}

function rmdir (p, originalEr, cb) {
  // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS)
  // if we guessed wrong, and it's not a directory, then
  // raise the original error.
  fs.rmdir(p, function (er) {
    if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST"))
      rmkids(p, cb)
    else if (er && er.code === "ENOTDIR")
      cb(originalEr)
    else
      cb(er)
  })
}

function rmkids(p, cb) {
  fs.readdir(p, function (er, files) {
    if (er)
      return cb(er)
    var n = files.length
    if (n === 0)
      return fs.rmdir(p, cb)
    var errState
    files.forEach(function (f) {
      rimraf(path.join(p, f), function (er) {
        if (errState)
          return
        if (er)
          return cb(errState = er)
        if (--n === 0)
          fs.rmdir(p, cb)
      })
    })
  })
}

// this looks simpler, and is strictly *faster*, but will
// tie up the JavaScript thread and fail on excessively
// deep directory trees.
function rimrafSync (p) {
  try {
    fs.unlinkSync(p)
  } catch (er) {
    if (er.code === "ENOENT")
      return
    if (er.code !== "EPERM" && er.code !== "EISDIR")
      throw er
    try {
      fs.rmdirSync(p)
    } catch (er2) {
      if (er2.code === "ENOENT")
        return
      if (er2.code === "ENOTDIR")
        throw er
      if (er2.code === "ENOTEMPTY") {
        fs.readdirSync(p).forEach(function (f) {
          rimrafSync(path.join(p, f))
        })
        fs.rmdirSync(p)
      }
    }
  }
}


ProjectModel.prototype.setContext = function (path) {

// This bit is not ideal: when the user right-clicks on an item in the file tree

// and there is already a context menu up, the FileTreeView sends a signal to set the

// context to the new element but the PopupManager follows that with a message that it's

// closing the context menu (because it closes the previous one and then opens the new

// one.) This timing means that we need to provide some special case handling here.

ProjectModel.prototype.setContext = function (path, _doNotRename) {


<!doctype html>

	<meta charset="utf-8">
	<title>jQuery UI Spinner - Default functionality</title>
	<link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">

	<link rel="stylesheet" href="../demos.css">

	Currency to donate
	<select id="currency" name="currency">
		<option value="en-US">US $</option>
		<option value="de-DE">EUR €</option>
		<option value="ja-JP">YEN ¥</option>
	</select>

	Amount to donate:
	<input id="spinner" name="spinner" value="5">

Example of a donation form, with currency selection and amout spinner.


var Chainsaw = require('chainsaw');
var EventEmitter = require('events').EventEmitter;
var Buffers = require('buffers');
var Vars = require('./lib/vars.js');
var Stream = require('stream').Stream;

exports = module.exports = function (bufOrEm, eventName) {
    if (Buffer.isBuffer(bufOrEm)) {
        return exports.parse(bufOrEm);
    }

    var s = exports.stream();
    if (bufOrEm && bufOrEm.pipe) {
        bufOrEm.pipe(s);
    }
    else if (bufOrEm) {
        bufOrEm.on(eventName || 'data', function (buf) {
            s.write(buf);
        });

        bufOrEm.on('end', function () {
            s.end();
        });
    }
    return s;
};

exports.stream = function (input) {
    if (input) return exports.apply(null, arguments);

    var pending = null;
    function getBytes (bytes, cb, skip) {
        pending = {
            bytes : bytes,
            skip : skip,
            cb : function (buf) {
                pending = null;
                cb(buf);
            },
        };
        dispatch();
    }

    var offset = null;
    function dispatch () {
        if (!pending) {
            if (caughtEnd) done = true;
            return;
        }
        if (typeof pending === 'function') {
            pending();
        }
        else {
            var bytes = offset + pending.bytes;

            if (buffers.length >= bytes) {
                var buf;
                if (offset == null) {
                    buf = buffers.splice(0, bytes);
                    if (!pending.skip) {
                        buf = buf.slice();
                    }
                }
                else {
                    if (!pending.skip) {
                        buf = buffers.slice(offset, bytes);
                    }
                    offset = bytes;
                }

                if (pending.skip) {
                    pending.cb();
                }
                else {
                    pending.cb(buf);
                }
            }
        }
    }

    function builder (saw) {
        function next () { if (!done) saw.next() }

        var self = words(function (bytes, cb) {
            return function (name) {
                getBytes(bytes, function (buf) {
                    vars.set(name, cb(buf));
                    next();
                });
            };
        });

        self.tap = function (cb) {
            saw.nest(cb, vars.store);
        };

        self.into = function (key, cb) {
            if (!vars.get(key)) vars.set(key, {});
            var parent = vars;
            vars = Vars(parent.get(key));

            saw.nest(function () {
                cb.apply(this, arguments);
                this.tap(function () {
                    vars = parent;
                });
            }, vars.store);
        };

        self.flush = function () {
            vars.store = {};
            next();
        };

        self.loop = function (cb) {
            var end = false;

            saw.nest(false, function loop () {
                this.vars = vars.store;
                cb.call(this, function () {
                    end = true;
                    next();
                }, vars.store);
                this.tap(function () {
                    if (end) saw.next()
                    else loop.call(this)
                }.bind(this));
            }, vars.store);
        };

        self.buffer = function (name, bytes) {
            if (typeof bytes === 'string') {
                bytes = vars.get(bytes);
            }

            getBytes(bytes, function (buf) {
                vars.set(name, buf);
                next();
            });
        };

        self.skip = function (bytes) {
            if (typeof bytes === 'string') {
                bytes = vars.get(bytes);
            }

            getBytes(bytes, function () {
                next();
            });
        };

        self.scan = function find (name, search) {
            if (typeof search === 'string') {
                search = new Buffer(search);
            }
            else if (!Buffer.isBuffer(search)) {
                throw new Error('search must be a Buffer or a string');
            }

            var taken = 0;
            pending = function () {
                var pos = buffers.indexOf(search, offset + taken);
                var i = pos-offset-taken;
                if (pos !== -1) {
                    pending = null;
                    if (offset != null) {
                        vars.set(
                            name,
                            buffers.slice(offset, offset + taken + i)
                        );
                        offset += taken + i + search.length;
                    }
                    else {
                        vars.set(
                            name,
                            buffers.slice(0, taken + i)
                        );
                        buffers.splice(0, taken + i + search.length);
                    }
                    next();
                    dispatch();
                } else {
                    i = Math.max(buffers.length - search.length - offset - taken, 0);
				}
                taken += i;
            };
            dispatch();
        };

        self.peek = function (cb) {
            offset = 0;
            saw.nest(function () {
                cb.call(this, vars.store);
                this.tap(function () {
                    offset = null;
                });
            });
        };

        return self;
    };

    var stream = Chainsaw.light(builder);
    stream.writable = true;

    var buffers = Buffers();

    stream.write = function (buf) {
        buffers.push(buf);
        dispatch();
    };

    var vars = Vars();

    var done = false, caughtEnd = false;
    stream.end = function () {
        caughtEnd = true;
    };

    stream.pipe = Stream.prototype.pipe;
    Object.getOwnPropertyNames(EventEmitter.prototype).forEach(function (name) {
        stream[name] = EventEmitter.prototype[name];
    });

    return stream;
};

exports.parse = function parse (buffer) {
    var self = words(function (bytes, cb) {
        return function (name) {
            if (offset + bytes <= buffer.length) {
                var buf = buffer.slice(offset, offset + bytes);
                offset += bytes;
                vars.set(name, cb(buf));
            }
            else {
                vars.set(name, null);
            }
            return self;
        };
    });

    var offset = 0;
    var vars = Vars();
    self.vars = vars.store;

    self.tap = function (cb) {
        cb.call(self, vars.store);
        return self;
    };

    self.into = function (key, cb) {
        if (!vars.get(key)) {
            vars.set(key, {});
        }
        var parent = vars;
        vars = Vars(parent.get(key));
        cb.call(self, vars.store);
        vars = parent;
        return self;
    };

    self.loop = function (cb) {
        var end = false;
        var ender = function () { end = true };
        while (end === false) {
            cb.call(self, ender, vars.store);
        }
        return self;
    };

    self.buffer = function (name, size) {
        if (typeof size === 'string') {
            size = vars.get(size);
        }
        var buf = buffer.slice(offset, Math.min(buffer.length, offset + size));
        offset += size;
        vars.set(name, buf);

        return self;
    };

    self.skip = function (bytes) {
        if (typeof bytes === 'string') {
            bytes = vars.get(bytes);
        }
        offset += bytes;

        return self;
    };

    self.scan = function (name, search) {
        if (typeof search === 'string') {
            search = new Buffer(search);
        }
        else if (!Buffer.isBuffer(search)) {
            throw new Error('search must be a Buffer or a string');
        }
        vars.set(name, null);

        // simple but slow string search
        for (var i = 0; i + offset <= buffer.length - search.length + 1; i++) {
            for (
                var j = 0;
                j < search.length && buffer[offset+i+j] === search[j];
                j++
            );
            if (j === search.length) break;
        }

        vars.set(name, buffer.slice(offset, offset + i));
        offset += i + search.length;
        return self;
    };

    self.peek = function (cb) {
        var was = offset;
        cb.call(self, vars.store);
        offset = was;
        return self;
    };

    self.flush = function () {
        vars.store = {};
        return self;
    };

    self.eof = function () {
        return offset >= buffer.length;
    };

    return self;
};

// convert byte strings to unsigned little endian numbers
function decodeLEu (bytes) {
    var acc = 0;
    for (var i = 0; i < bytes.length; i++) {
        acc += Math.pow(256,i) * bytes[i];
    }
    return acc;
}

// convert byte strings to unsigned big endian numbers
function decodeBEu (bytes) {
    var acc = 0;
    for (var i = 0; i < bytes.length; i++) {
        acc += Math.pow(256, bytes.length - i - 1) * bytes[i];
    }
    return acc;
}

// convert byte strings to signed big endian numbers
function decodeBEs (bytes) {
    var val = decodeBEu(bytes);
    if ((bytes[0] & 0x80) == 0x80) {
        val -= Math.pow(256, bytes.length);
    }
    return val;
}

// convert byte strings to signed little endian numbers
function decodeLEs (bytes) {
    var val = decodeLEu(bytes);
    if ((bytes[bytes.length - 1] & 0x80) == 0x80) {
        val -= Math.pow(256, bytes.length);
    }
    return val;
}

function words (decode) {
    var self = {};

    [ 1, 2, 4, 8 ].forEach(function (bytes) {
        var bits = bytes * 8;

        self['word' + bits + 'le']
        = self['word' + bits + 'lu']
        = decode(bytes, decodeLEu);

        self['word' + bits + 'ls']
        = decode(bytes, decodeLEs);

        self['word' + bits + 'be']
        = self['word' + bits + 'bu']
        = decode(bytes, decodeBEu);

        self['word' + bits + 'bs']
        = decode(bytes, decodeBEs);
    });

    // word8be(n) == word8le(n) for all n
    self.word8 = self.word8u = self.word8be;
    self.word8s = self.word8bs;

    return self;
}