/*
 * 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, maxerr: 50 */
/*global define, $, window */

/**
 * Manages the workingSet sort methods.
 */
define(function (require, exports, module) {
    "use strict";

    var Commands                = require("command/Commands"),
        CommandManager          = require("command/CommandManager"),
        DocumentManager         = require("document/DocumentManager"),
        PreferencesManager      = require("preferences/PreferencesManager"),
        FileUtils               = require("file/FileUtils"),
        AppInit                 = require("utils/AppInit"),
        Strings                 = require("strings");

    var defaultPrefs = {
        currentSort:   Commands.SORT_WORKINGSET_BY_ADDED,
        automaticSort: false
    };

    /**
     * @private
     * @type {PreferenceStorage}
     */
    var _prefs = {};

    /**
     * @private
     * @type {Array.<Sort>}
     */
    var _sorts = [];

    /**
     * @private
     * @type {Sort}
     */
    var _currentSort = null;

    /**
     * @private
     * @type {boolean}
     */
    var _automaticSort = false;

    /**
     * @private
     * @type {boolean}
     * Used to know when to do the automatic sort for MRU order.
     */
    var _openedDocument = false;

    /**
     * Retrieves a Sort object by id
     * @param {(string|Command)} command A command ID or a command object.
     * @return {?Sort}
     */
    function get(command) {
        var commandID;
        if (!command) {
            console.error("Attempting to get a Sort method with a missing required parameter: command");
            return;
        }

        if (typeof command === "string") {
            commandID = command;
        } else {
            commandID = command.getID();
        }
        return _sorts[commandID];
    }

    /**
     * @return {boolean} Enabled state of Automatic Sort.
     */
    function getAutomatic() {
        return _automaticSort;
    }

    /**
     * @private
     * Removes the sort DocumentManager listeners.
     */
    function _removeListeners() {
        $(DocumentManager).off(".sort");
    }

    /**
     * Enables/Disables Automatic Sort depending on the value.
     * @param {boolean} enable True to enable, false to disable.
     */
    function setAutomatic(enable) {
        _automaticSort = enable;
        _prefs.setValue("automaticSort", _automaticSort);
        CommandManager.get(Commands.SORT_WORKINGSET_AUTO).setChecked(_automaticSort);

        if (enable) {
            _currentSort.sort();
        } else {
            _removeListeners();
        }
    }

    /**
     * @private
     * Adds the current sort DocumentManager listeners.
     */
    function _addListeners() {
        if (_automaticSort && _currentSort && _currentSort.getEvents()) {
            $(DocumentManager)
                .on(_currentSort.getEvents(), function () {
                    _currentSort.sort();
                })
                .on("workingSetDisableAutoSorting.sort", function () {
                    setAutomatic(false);
                });
        }
    }

    /**
     * @private
     * Sets the current sort method and checks it on the context menu.
     * @param {Sort} newSort
     */
    function _setCurrentSort(newSort) {
        var command;
        if (_currentSort !== newSort) {
            if (_currentSort !== null) {
                command = CommandManager.get(_currentSort.getCommandID());
                if (command) {
                    command.setChecked(false);
                }
            }
            command = CommandManager.get(newSort.getCommandID());
            if (command) {
                command.setChecked(true);
            }

            CommandManager.get(Commands.SORT_WORKINGSET_AUTO).setEnabled(!!newSort.getEvents());
            _currentSort = newSort;
            _prefs.setValue("currentSort", _currentSort.getCommandID());
        }
    }

    /**
     * @constructor
     * @private
     *
     * @param {string} commandID A valid command identifier.
     * @param {function(File, File): number} compareFn A valid sort
     *      function (see register for a longer explanation).
     * @param {string} events Space-separated DocumentManager possible events
     *      ending with ".sort".
     */
    function Sort(commandID, compareFn, events, automaticFn) {
        this._commandID = commandID;
        this._compareFn = compareFn;
        this._events    = events;
    }

    /** @return {string} The Command ID */
    Sort.prototype.getCommandID = function () {
        return this._commandID;
    };

    /** @return {function(File, File): number} The compare function */
    Sort.prototype.getCompareFn = function () {
        return this._compareFn;
    };

    /** @return {string} The DocumentManager events */
    Sort.prototype.getEvents = function () {
        return this._events;
    };

    /**
     * Performs the sort and makes it the current sort method.
     */
    Sort.prototype.execute = function () {
        _setCurrentSort(this);
        this.sort();
    };

    /**
     * Only performs the working set sort if this is the current sort.
     */
    Sort.prototype.sort = function () {
        if (_currentSort === this) {
            _removeListeners();
            DocumentManager.sortWorkingSet(this._compareFn);
            _addListeners();
        }
    };

    /**
     * Registers a working set sort method.
     * @param {(string|Command)} command A command ID or a command object
     * @param {function(File, File): number} compareFn The function that
     *      will be used inside JavaScript's sort function. The return a value
     *      should be >0 (sort a to a lower index than b), =0 (leaves a and b
     *      unchanged with respect to each other) or <0 (sort b to a lower index
     *      than a) and must always returns the same value when given a specific
     *      pair of elements a and b as its two arguments. Documentation at:
     *      https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort
     * @param {?string} events One or more space-separated event types that
     *      DocumentManger uses. Each event passed will trigger the automatic
     *      sort. If no events are passed, the automatic sort will be disabled
     *      for that sort method.
     * @return {?Sort}
     */
    function register(command, compareFn, events) {
        var commandID = "";

        if (!command || !compareFn) {
            console.log("Attempting to register a Sort method with a missing required parameter: command or compare function");
            return;
        }
        if (typeof command === "string") {
            commandID = command;
        } else {
            commandID = command.getID();
        }

        if (_sorts[commandID]) {
            console.log("Attempting to register an already-registered Sort method: " + command);
            return;
        }

        // Adds ".sort" to the end of each event to make them specific for the automatic sort.
        if (events) {
            events = events.split(" ");
            events.forEach(function (event, index) {
                events[index] = events[index].trim() + ".sort";
            });
            events = events.join(" ");
        }

        var sort = new Sort(commandID, compareFn, events);
        _sorts[commandID] = sort;
        return sort;
    }

    /** Command Handlers */
    function _handleSortWorkingSetByAdded() {
        get(Commands.SORT_WORKINGSET_BY_ADDED).execute();
    }

    function _handleSortWorkingSetByName() {
        get(Commands.SORT_WORKINGSET_BY_NAME).execute();
    }

    function _handleSortWorkingSetByType() {
        get(Commands.SORT_WORKINGSET_BY_TYPE).execute();
    }

    function _handleAutomaticSort() {
        setAutomatic(!getAutomatic());
    }

    // Register Sort Methods
    register(
        Commands.SORT_WORKINGSET_BY_ADDED,
        function (file1, file2) {
            var index1 = DocumentManager.findInWorkingSetAddedOrder(file1.fullPath),
                index2 = DocumentManager.findInWorkingSetAddedOrder(file2.fullPath);

            return index1 - index2;
        },
        "workingSetAdd workingSetAddList"
    );
    register(
        Commands.SORT_WORKINGSET_BY_NAME,
        function (file1, file2) {
            return FileUtils.compareFilenames(file1.name, file2.name, false);
        },
        "workingSetAdd workingSetAddList"
    );
    register(
        Commands.SORT_WORKINGSET_BY_TYPE,
        function (file1, file2) {
            return FileUtils.compareFilenames(file1.name, file2.name, true);
        },
        "workingSetAdd workingSetAddList"
    );

    // Register Command Handlers
    CommandManager.register(Strings.CMD_SORT_WORKINGSET_BY_ADDED, Commands.SORT_WORKINGSET_BY_ADDED, _handleSortWorkingSetByAdded);
    CommandManager.register(Strings.CMD_SORT_WORKINGSET_BY_NAME,  Commands.SORT_WORKINGSET_BY_NAME,  _handleSortWorkingSetByName);
    CommandManager.register(Strings.CMD_SORT_WORKINGSET_BY_TYPE,  Commands.SORT_WORKINGSET_BY_TYPE,  _handleSortWorkingSetByType);
    CommandManager.register(Strings.CMD_SORT_WORKINGSET_AUTO,     Commands.SORT_WORKINGSET_AUTO,     _handleAutomaticSort);

    // Initialize PreferenceStorage
    _prefs = PreferencesManager.getPreferenceStorage(module, defaultPrefs);

    // Initialize items dependent on extensions/workingSet
    AppInit.appReady(function () {
        var curSort  = get(_prefs.getValue("currentSort")),
            autoSort = _prefs.getValue("automaticSort");

        if (curSort) {
            _setCurrentSort(curSort);
        }
        if (autoSort) {
            setAutomatic(true);
        }
        if (curSort && autoSort) {
            curSort.sort();
        }
    });

    // Define public API
    exports.register        = register;
    exports.get             = get;
    exports.getAutomatic    = getAutomatic;
    exports.setAutomatic    = setAutomatic;
});


var languageLayer = new PreferencesBase.LanguageLayer();

var pathLayer = new PreferencesBase.PathLayer();

// "State" is stored like preferences but it is not generally intended to be user-editable.

// It's for more internal, implicit things like window size, working set, etc.

var stateManager = new PreferencesBase.PreferencesManager();

var userStateFile = brackets.app.getApplicationSupportDirectory() + "/" + SETTINGS_FILENAME;

keep keep keep keep replace replace replace replace replace replace replace keep keep keep keep

@charset "utf-8";
#container {
	width: 100%;
}
#logo {
	height:auto;
	width:270px;
	background-image: url(../images/sml_logo.png);
	margin-left:auto;
	margin-right:auto;
}
nav {
	padding-top:100px;
}
nav ul {
	font-size:24px;
}
nav ul a {
	width:97%;
	padding-right:0;
}
nav ul li {
	float:none;
}
nav ul li:hover {
	margin-top:0;
}
#maincontent {
	margin-top: 0;
	padding:0;
}
#vision {
	background-image:none;
	width: 280px;
	height:auto;
	font-size:16px;
	line-height:normal;
	padding-top:0;
}
/* Uncomment the declarations in the following rules to hide the images in the pods. */
.pod {
	width: 305px;
	/*padding-bottom:0;*/
}
/*.pod h1 {
	margin-bottom:0;
}*/
.podContent {
	width: 302px;
	height:180px;
	/*display:none;*/
}
/*#news .podContent {
	display:block;
}*/
footer p {
	margin-left:5px;
}
#facebookTwitter {
	margin-right:5px;
}


// Private API for unit testing and use elsewhere in Brackets core

keep keep keep replace replace keep keep keep

//    exports._setCurrentEditingFile = pathLayer.setFilename.bind(pathLayer);

/*
 * 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, maxerr: 50 */
/*global define, $ */

/**
 * DocumentManager maintains a list of currently 'open' Documents. It also owns the list of files in
 * the working set, and the notion of which Document is currently shown in the main editor UI area.
 *
 * Document is the model for a file's contents; it dispatches events whenever those contents change.
 * To transiently inspect a file's content, simply get a Document and call getText() on it. However,
 * to be notified of Document changes or to modify a Document, you MUST call addRef() to ensure the
 * Document instance 'stays alive' and is shared by all other who read/modify that file. ('Open'
 * Documents are all Documents that are 'kept alive', i.e. have ref count > 0).
 *
 * To get a Document, call getDocumentForPath(); never new up a Document yourself.
 *
 * Secretly, a Document may use an Editor instance to act as the model for its internal state. (This
 * is unavoidable because CodeMirror does not separate its model from its UI). Documents are not
 * modifiable until they have a backing 'master Editor'. Creation of the backing Editor is owned by
 * EditorManager. A Document only gets a backing Editor if it becomes the currentDocument, or if edits
 * occur in any Editor (inline or full-sized) bound to the Document; there is currently no other way
 * to ensure a Document is modifiable.
 *
 * A non-modifiable Document may still dispatch change notifications, if the Document was changed
 * externally on disk.
 *
 * Aside from the text content, Document tracks a few pieces of metadata - notably, whether there are
 * any unsaved changes.
 *
 * This module dispatches several events:
 *
 *    - dirtyFlagChange -- When any Document's isDirty flag changes. The 2nd arg to the listener is the
 *      Document whose flag changed.
 *    - documentSaved -- When a Document's changes have been saved. The 2nd arg to the listener is the
 *      Document that has been saved.
 *    - documentRefreshed -- When a Document's contents have been reloaded from disk. The 2nd arg to the
 *      listener is the Document that has been refreshed.
 *
 *    - currentDocumentChange -- When the value of getCurrentDocument() changes.
 *
 *    To listen for working set changes, you must listen to *all* of these events:
 *    - workingSetAdd -- When a file is added to the working set (see getWorkingSet()). The 2nd arg
 *      to the listener is the added File, and the 3rd arg is the index it was inserted at.
 *    - workingSetAddList -- When multiple files are added to the working set (e.g. project open, multiple file open).
 *      The 2nd arg to the listener is the array of added File objects.
 *    - workingSetRemove -- When a file is removed from the working set (see getWorkingSet()). The
 *      2nd arg to the listener is the removed File.
 *    - workingSetRemoveList -- When multiple files are removed from the working set (e.g. project close).
 *      The 2nd arg to the listener is the array of removed File objects.
 *    - workingSetSort -- When the workingSet array is reordered without additions or removals.
 *      Listener receives no arguments.
 *
 *    - workingSetDisableAutoSorting -- Dispatched in addition to workingSetSort when the reorder was caused
 *      by manual dragging and dropping. Listener receives no arguments.
 *
 *    - fileNameChange -- When the name of a file or folder has changed. The 2nd arg is the old name.
 *      The 3rd arg is the new name.
 *    - pathDeleted -- When a file or folder has been deleted. The 2nd arg is the path that was deleted.
 *
 * These are jQuery events, so to listen for them you do something like this:
 *    $(DocumentManager).on("eventname", handler);
 *
 * Document objects themselves also dispatch some events - see Document docs for details.
 */
define(function (require, exports, module) {
    "use strict";

    var _ = require("thirdparty/lodash");

    var DocumentModule      = require("document/Document"),
        ProjectManager      = require("project/ProjectManager"),
        EditorManager       = require("editor/EditorManager"),
        FileSyncManager     = require("project/FileSyncManager"),
        FileSystem          = require("filesystem/FileSystem"),
        PreferencesManager  = require("preferences/PreferencesManager"),
        FileUtils           = require("file/FileUtils"),
        InMemoryFile        = require("document/InMemoryFile"),
        CommandManager      = require("command/CommandManager"),
        Async               = require("utils/Async"),
        PerfUtils           = require("utils/PerfUtils"),
        Commands            = require("command/Commands"),
        LanguageManager     = require("language/LanguageManager"),
        Strings             = require("strings");

    /**
     * @private
     * @see DocumentManager.getCurrentDocument()
     */
    var _currentDocument = null;

    /**
     * @private
     * @type {PreferenceStorage}
     */
    var _prefs = {};

    /**
     * Returns the Document that is currently open in the editor UI. May be null.
     * When this changes, DocumentManager dispatches a "currentDocumentChange" event. The current
     * document always has a backing Editor (Document._masterEditor != null) and is thus modifiable.
     * @return {?Document}
     */
    function getCurrentDocument() {
        return _currentDocument;
    }

    /**
     * @private
     * Random path prefix for untitled documents
     */
    var _untitledDocumentPath = "/_brackets_" + _.random(10000000, 99999999);

    /**
     * @private
     * @type {Array.<File>}
     * @see DocumentManager.getWorkingSet()
     */
    var _workingSet = [];

    /**
     * @private
     * Contains the same set of items as _workingSet, but ordered by how recently they were _currentDocument (0 = most recent).
     * @type {Array.<File>}
     */
    var _workingSetMRUOrder = [];

    /**
     * @private
     * Contains the same set of items as _workingSet, but ordered in the way they where added to _workingSet (0 = last added).
     * @type {Array.<File>}
     */
    var _workingSetAddedOrder = [];

    /**
     * While true, the MRU order is frozen
     * @type {boolean}
     */
    var _documentNavPending = false;

    /**
     * All documents with refCount > 0. Maps Document.file.id -> Document.
     * @private
     * @type {Object.<string, Document>}
     */
    var _openDocuments = {};

    /**
     * Returns a list of items in the working set in UI list order. May be 0-length, but never null.
     *
     * When a file is added this list, DocumentManager dispatches a "workingSetAdd" event.
     * When a file is removed from list, DocumentManager dispatches a "workingSetRemove" event.
     * To listen for ALL changes to this list, you must listen for both events.
     *
     * Which items belong in the working set is managed entirely by DocumentManager. Callers cannot
     * (yet) change this collection on their own.
     *
     * @return {Array.<File>}
     */
    function getWorkingSet() {
        return _.clone(_workingSet);
    }

    /**
     * Returns the index of the file matching fullPath in the working set.
     * Returns -1 if not found.
     * @param {!string} fullPath
     * @param {Array.<File>=} list Pass this arg to search a different array of files. Internal
     *          use only.
     * @returns {number} index
     */
    function findInWorkingSet(fullPath, list) {
        list = list || _workingSet;

        return _.findIndex(list, function (file, i) {
            return file.fullPath === fullPath;
        });
    }

    /**
     * Returns the index of the file matching fullPath in _workingSetAddedOrder.
     * Returns -1 if not found.
     * @param {!string} fullPath
     * @returns {number} index
     */
    function findInWorkingSetAddedOrder(fullPath) {
        return findInWorkingSet(fullPath, _workingSetAddedOrder);
    }

    /**
     * Returns all Documents that are 'open' in the UI somewhere (for now, this means open in an
     * inline editor and/or a full-size editor). Only these Documents can be modified, and only
     * these Documents are synced with external changes on disk.
     * @return {Array.<Document>}
     */
    function getAllOpenDocuments() {
        var result = [];
        var id;
        for (id in _openDocuments) {
            if (_openDocuments.hasOwnProperty(id)) {
                result.push(_openDocuments[id]);
            }
        }
        return result;
    }

    /**
     * Adds the given file to the end of the working set list, if it is not already in the list
     * and it does not have a custom viewer.
     * Does not change which document is currently open in the editor. Completes synchronously.
     * @param {!File} file
     * @param {number=} index  Position to add to list (defaults to last); -1 is ignored
     * @param {boolean=} forceRedraw  If true, a working set change notification is always sent
     *    (useful if suppressRedraw was used with removeFromWorkingSet() earlier)
     */
    function addToWorkingSet(file, index, forceRedraw) {
        var indexRequested = (index !== undefined && index !== null && index !== -1);

        // If the file has a custom viewer, then don't add it to the working set.
        if (EditorManager.getCustomViewerForPath(file.fullPath)) {
            return;
        }

        // If doc is already in working set, don't add it again
        var curIndex = findInWorkingSet(file.fullPath);
        if (curIndex !== -1) {
            // File is in working set, but not at the specifically requested index - only need to reorder
            if (forceRedraw || (indexRequested && curIndex !== index)) {
                var entry = _workingSet.splice(curIndex, 1)[0];
                _workingSet.splice(index, 0, entry);
                $(exports).triggerHandler("workingSetSort");
            }
            return;
        }

        if (!indexRequested) {
            // If no index is specified, just add the file to the end of the working set.
            _workingSet.push(file);
        } else {
            // If specified, insert into the working set list at this 0-based index
            _workingSet.splice(index, 0, file);
        }

        // Add to MRU order: either first or last, depending on whether it's already the current doc or not
        if (_currentDocument && _currentDocument.file.fullPath === file.fullPath) {
            _workingSetMRUOrder.unshift(file);
        } else {
            _workingSetMRUOrder.push(file);
        }

        // Add first to Added order
        _workingSetAddedOrder.unshift(file);

        // Dispatch event
        if (!indexRequested) {
            index = _workingSet.length - 1;
        }
        $(exports).triggerHandler("workingSetAdd", [file, index]);
    }

    /**
     * Adds the given file list to the end of the working set list.
     * If a file in the list has its own custom viewer, then it
     * is not added into the working set.
     * Does not change which document is currently open in the editor.
     * More efficient than calling addToWorkingSet() (in a loop) for
     * a list of files because there's only 1 redraw at the end
     * @param {!Array.<File>} fileList
     */
    function addListToWorkingSet(fileList) {
        var uniqueFileList = [];

        // Process only files not already in working set
        fileList.forEach(function (file, index) {
            // If doc has a custom viewer, then don't add it to the working set.
            // Or if doc is already in working set, don't add it again.
            if (!EditorManager.getCustomViewerForPath(file.fullPath) &&
                    findInWorkingSet(file.fullPath) === -1) {
                uniqueFileList.push(file);

                // Add
                _workingSet.push(file);

                // Add to MRU order: either first or last, depending on whether it's already the current doc or not
                if (_currentDocument && _currentDocument.file.fullPath === file.fullPath) {
                    _workingSetMRUOrder.unshift(file);
                } else {
                    _workingSetMRUOrder.push(file);
                }

                // Add first to Added order
                _workingSetAddedOrder.splice(index, 1, file);
            }
        });

        // Dispatch event
        $(exports).triggerHandler("workingSetAddList", [uniqueFileList]);
    }

    /**
     * Warning: low level API - use FILE_CLOSE command in most cases.
     * Removes the given file from the working set list, if it was in the list. Does not change
     * the current editor even if it's for this file. Does not prompt for unsaved changes.
     * @param {!File} file
     * @param {boolean=} true to suppress redraw after removal
     */
    function removeFromWorkingSet(file, suppressRedraw) {
        // If doc isn't in working set, do nothing
        var index = findInWorkingSet(file.fullPath);
        if (index === -1) {
            return;
        }

        // Remove
        _workingSet.splice(index, 1);
        _workingSetMRUOrder.splice(findInWorkingSet(file.fullPath, _workingSetMRUOrder), 1);
        _workingSetAddedOrder.splice(findInWorkingSet(file.fullPath, _workingSetAddedOrder), 1);

        // Dispatch event
        $(exports).triggerHandler("workingSetRemove", [file, suppressRedraw]);
    }

    /**
     * Removes all files from the working set list.
     */
    function _removeAllFromWorkingSet() {
        var fileList = _workingSet;

        // Remove all
        _workingSet = [];
        _workingSetMRUOrder = [];
        _workingSetAddedOrder = [];

        // Dispatch event
        $(exports).triggerHandler("workingSetRemoveList", [fileList]);
    }

    /**
     * Moves document to the front of the MRU list, IF it's in the working set; no-op otherwise.
     * @param {!Document}
     */
    function _markMostRecent(doc) {
        var mruI = findInWorkingSet(doc.file.fullPath, _workingSetMRUOrder);
        if (mruI !== -1) {
            _workingSetMRUOrder.splice(mruI, 1);
            _workingSetMRUOrder.unshift(doc.file);
        }
    }

    /**
     * Mutually exchanges the files at the indexes passed by parameters.
     * @param {number} index  Old file index
     * @param {number} index  New file index
     */
    function swapWorkingSetIndexes(index1, index2) {
        var length = _workingSet.length - 1;
        var temp;

        if (index1 >= 0 && index2 <= length && index1 >= 0 && index2 <= length) {
            temp = _workingSet[index1];
            _workingSet[index1] = _workingSet[index2];
            _workingSet[index2] = temp;

            $(exports).triggerHandler("workingSetSort");
            $(exports).triggerHandler("workingSetDisableAutoSorting");
        }
    }

    /**
     * Sorts _workingSet using the compare function
     * @param {function(File, File): number} compareFn  The function that will be used inside JavaScript's
     *      sort function. The return a value should be >0 (sort a to a lower index than b), =0 (leaves a and b
     *      unchanged with respect to each other) or <0 (sort b to a lower index than a) and must always returns
     *      the same value when given a specific pair of elements a and b as its two arguments.
     *      Documentation: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort
     */
    function sortWorkingSet(compareFn) {
        _workingSet.sort(compareFn);
        $(exports).triggerHandler("workingSetSort");
    }

    /**
     * Indicate that changes to currentDocument are temporary for now, and should not update the MRU
     * ordering of the working set. Useful for next/previous keyboard navigation (until Ctrl is released)
     * or for incremental-search style document preview like Quick Open will eventually have.
     * Can be called any number of times, and ended by a single finalizeDocumentNavigation() call.
     */
    function beginDocumentNavigation() {
        _documentNavPending = true;
    }

    /**
     * Un-freezes the MRU list after one or more beginDocumentNavigation() calls. Whatever document is
     * current is bumped to the front of the MRU list.
     */
    function finalizeDocumentNavigation() {
        if (_documentNavPending) {
            _documentNavPending = false;

            _markMostRecent(_currentDocument);
        }
    }

    /**
     * Get the next or previous file in the working set, in MRU order (relative to currentDocument). May
     * return currentDocument itself if working set is length 1.
     * @param {number} inc  -1 for previous, +1 for next; no other values allowed
     * @return {?File}  null if working set empty
     */
    function getNextPrevFile(inc) {
        if (inc !== -1 && inc !== +1) {
            console.error("Illegal argument: inc = " + inc);
            return null;
        }

        if (EditorManager.getCurrentlyViewedPath()) {
            var mruI = findInWorkingSet(EditorManager.getCurrentlyViewedPath(), _workingSetMRUOrder);
            if (mruI === -1) {
                // If doc not in working set, return most recent working set item
                if (_workingSetMRUOrder.length > 0) {
                    return _workingSetMRUOrder[0];
                }
            } else {
                // If doc is in working set, return next/prev item with wrap-around
                var newI = mruI + inc;
                if (newI >= _workingSetMRUOrder.length) {
                    newI = 0;
                } else if (newI < 0) {
                    newI = _workingSetMRUOrder.length - 1;
                }

                return _workingSetMRUOrder[newI];
            }
        }

        // If no doc open or working set empty, there is no "next" file
        return null;
    }

    /**
     * Changes currentDocument to the given Document, firing currentDocumentChange, which in turn
     * causes this Document's main editor UI to be shown in the editor pane, updates the selection
     * in the file tree / working set UI, etc. This call may also add the item to the working set.
     *
     * @param {!Document} document  The Document to make current. May or may not already be in the
     *      working set.
     */
    function setCurrentDocument(doc) {

        // If this doc is already current, do nothing
        if (_currentDocument === doc) {
            return;
        }

        var perfTimerName = PerfUtils.markStart("setCurrentDocument:\t" + doc.file.fullPath);

        // If file is untitled or otherwise not within project tree, add it to
        // working set right now (don't wait for it to become dirty)
        if (doc.isUntitled() || !ProjectManager.isWithinProject(doc.file.fullPath)) {
            addToWorkingSet(doc.file);
        }

        // Adjust MRU working set ordering (except while in the middle of a Ctrl+Tab sequence)
        if (!_documentNavPending) {
            _markMostRecent(doc);
        }

        // Make it the current document
        _currentDocument = doc;
        $(exports).triggerHandler("currentDocumentChange");
        // (this event triggers EditorManager to actually switch editors in the UI)

        // Set the language layer in the preferences.
        if (doc.language) {
            PreferencesManager._setLanguage(doc.language.getId());
        } else {
            PreferencesManager._setLanguage();
        }

        PerfUtils.addMeasurement(perfTimerName);
    }

    /** Changes currentDocument to null, causing no full Editor to be shown in the UI */
    function _clearCurrentDocument() {
        // If editor already blank, do nothing
        if (!_currentDocument) {
            return;
        } else {
            // Change model & dispatch event
            _currentDocument = null;
            // (this event triggers EditorManager to actually clear the editor UI)
            $(exports).triggerHandler("currentDocumentChange");
        }
    }

    /**
     * Warning: low level API - use FILE_CLOSE command in most cases.
     * Closes the full editor for the given file (if there is one), and removes it from the working
     * set. Any other editors for this Document remain open. Discards any unsaved changes without prompting.
     *
     * Changes currentDocument if this file was the current document (may change to null).
     *
     * This is a subset of notifyFileDeleted(). Use this for the user-facing Close command.
     *
     * @param {!File} file
     * @param {boolean} skipAutoSelect - if true, don't automatically open and select the next document
     */
    function closeFullEditor(file, skipAutoSelect) {
        // If this was the current document shown in the editor UI, we're going to switch to a
        // different document (or none if working set has no other options)
        if (_currentDocument && _currentDocument.file.fullPath === file.fullPath) {
            // Get next most recent doc in the MRU order
            var nextFile = getNextPrevFile(1);
            if (nextFile && nextFile.fullPath === _currentDocument.file.fullPath) {
                // getNextPrevFile() might return the file we're about to close if it's the only one open (due to wraparound)
                nextFile = null;
            }

            // Switch editor to next document (or blank it out)
            if (nextFile && !skipAutoSelect) {
                CommandManager.execute(Commands.FILE_OPEN, { fullPath: nextFile.fullPath })
                    .done(function () {
                        // (Now we're guaranteed that the current document is not the one we're closing)
                        console.assert(!(_currentDocument && _currentDocument.file.fullPath === file.fullPath));
                    })
                    .fail(function () {
                        // File chosen to be switched to could not be opened, and the original file
                        // is still in editor. Close it again so code will try to open the next file,
                        // or empty the editor if there are no other files.
                        closeFullEditor(file);
                    });
            } else {
                _clearCurrentDocument();
            }
        }

        // Remove closed doc from working set, if it was in there
        // This happens regardless of whether the document being closed was the current one or not
        removeFromWorkingSet(file);

        // Note: EditorManager will dispose the closed document's now-unneeded editor either in
        // response to the editor-swap call above, or the removeFromWorkingSet() call, depending on
        // circumstances. See notes in EditorManager for more.
    }

    /**
     * Equivalent to calling closeFullEditor() for all Documents. Same caveat: this discards any
     * unsaved changes, so the UI should confirm with the user before calling this.
     */
    function closeAll() {
        _clearCurrentDocument();
        _removeAllFromWorkingSet();
    }

    function removeListFromWorkingSet(list, clearCurrentDocument) {
        var fileList = [], index;

        if (!list) {
            return;
        }

        if (clearCurrentDocument) {
            _clearCurrentDocument();
        }

        list.forEach(function (file) {
            index = findInWorkingSet(file.fullPath);

            if (index !== -1) {
                fileList.push(_workingSet[index]);

                _workingSet.splice(index, 1);
                _workingSetMRUOrder.splice(findInWorkingSet(file.fullPath, _workingSetMRUOrder), 1);
                _workingSetAddedOrder.splice(findInWorkingSet(file.fullPath, _workingSetAddedOrder), 1);
            }
        });

        $(exports).triggerHandler("workingSetRemoveList", [fileList]);
    }

    /**
     * Cleans up any loose Documents whose only ref is its own master Editor, and that Editor is not
     * rooted in the UI anywhere. This can happen if the Editor is auto-created via Document APIs that
     * trigger _ensureMasterEditor() without making it dirty. E.g. a command invoked on the focused
     * inline editor makes no-op edits or does a read-only operation.
     */
    function _gcDocuments() {
        getAllOpenDocuments().forEach(function (doc) {
            // Is the only ref to this document its own master Editor?
            if (doc._refCount === 1 && doc._masterEditor) {
                // Destroy the Editor if it's not being kept alive by the UI
                EditorManager._destroyEditorIfUnneeded(doc);
            }
        });
    }

    /**
     * Returns the existing open Document for the given file, or null if the file is not open ('open'
     * means referenced by the UI somewhere). If you will hang onto the Document, you must addRef()
     * it; see {@link getDocumentForPath()} for details.
     * @param {!string} fullPath
     * @return {?Document}
     */
    function getOpenDocumentForPath(fullPath) {
        var id;

        // Need to walk all open documents and check for matching path. We can't
        // use getFileForPath(fullPath).id since the file it returns won't match
        // an Untitled document's InMemoryFile.
        for (id in _openDocuments) {
            if (_openDocuments.hasOwnProperty(id)) {
                if (_openDocuments[id].file.fullPath === fullPath) {
                    return _openDocuments[id];
                }
            }
        }
        return null;
    }

    /**
     * Gets an existing open Document for the given file, or creates a new one if the Document is
     * not currently open ('open' means referenced by the UI somewhere). Always use this method to
     * get Documents; do not call the Document constructor directly. This method is safe to call
     * in parallel.
     *
     * If you are going to hang onto the Document for more than just the duration of a command - e.g.
     * if you are going to display its contents in a piece of UI - then you must addRef() the Document
     * and listen for changes on it. (Note: opening the Document in an Editor automatically manages
     * refs and listeners for that Editor UI).
     *
     * If all you need is the Document's getText() value, use the faster getDocumentText() instead.
     *
     * @param {!string} fullPath
     * @return {$.Promise} A promise object that will be resolved with the Document, or rejected
     *      with a FileSystemError if the file is not yet open and can't be read from disk.
     */
    function getDocumentForPath(fullPath) {
        var doc = getOpenDocumentForPath(fullPath);

        if (doc) {
            // use existing document
            return new $.Deferred().resolve(doc).promise();
        } else {

            // Should never get here if the fullPath refers to an Untitled document
            if (fullPath.indexOf(_untitledDocumentPath) === 0) {
                console.error("getDocumentForPath called for non-open untitled document: " + fullPath);
                return new $.Deferred().reject().promise();
            }

            var file            = FileSystem.getFileForPath(fullPath),
                pendingPromise  = getDocumentForPath._pendingDocumentPromises[file.id];

            if (pendingPromise) {
                // wait for the result of a previous request
                return pendingPromise;
            } else {
                var result = new $.Deferred(),
                    promise = result.promise();

                // log this document's Promise as pending
                getDocumentForPath._pendingDocumentPromises[file.id] = promise;

                // create a new document
                var perfTimerName = PerfUtils.markStart("getDocumentForPath:\t" + fullPath);

                result.done(function () {
                    PerfUtils.addMeasurement(perfTimerName);
                }).fail(function () {
                    PerfUtils.finalizeMeasurement(perfTimerName);
                });

                FileUtils.readAsText(file)
                    .always(function () {
                        // document is no longer pending
                        delete getDocumentForPath._pendingDocumentPromises[file.id];
                    })
                    .done(function (rawText, readTimestamp) {
                        doc = new DocumentModule.Document(file, readTimestamp, rawText);

                        // This is a good point to clean up any old dangling Documents
                        _gcDocuments();

                        result.resolve(doc);
                    })
                    .fail(function (fileError) {
                        result.reject(fileError);
                    });

                return promise;
            }
        }
    }

    /**
     * Document promises that are waiting to be resolved. It is possible for multiple clients
     * to request the same document simultaneously before the initial request has completed.
     * In particular, this happens at app startup where the working set is created and the
     * intial active document is opened in an editor. This is essential to ensure that only
     * one Document exists for any File.
     * @private
     * @type {Object.<string, $.Promise>}
     */
    getDocumentForPath._pendingDocumentPromises = {};

    /**
     * Gets the text of a Document (including any unsaved changes), or would-be Document if the
     * file is not actually open. More efficient than getDocumentForPath(). Use when you're reading
     * document(s) but don't need to hang onto a Document object.
     *
     * If the file is open this is equivalent to calling getOpenDocumentForPath().getText(). If the
     * file is NOT open, this is like calling getDocumentForPath()...getText() but more efficient.
     * Differs from plain FileUtils.readAsText() in two ways: (a) line endings are still normalized
     * as in Document.getText(); (b) unsaved changes are returned if there are any.
     *
     * @param {!File} file
     * @return {!string}
     */
    function getDocumentText(file) {
        var result = new $.Deferred(),
            doc = getOpenDocumentForPath(file.fullPath);
        if (doc) {
            result.resolve(doc.getText());
        } else {
            file.read(function (err, contents) {
                if (err) {
                    result.reject(err);
                } else {
                    // Normalize line endings the same way Document would, but don't actually
                    // new up a Document (which entails a bunch of object churn).
                    contents = DocumentModule.Document.normalizeText(contents);
                    result.resolve(contents);
                }
            });
        }
        return result.promise();
    }

    /**
     * Creates an untitled document. The associated File has a fullPath that
     * looks like /some-random-string/Untitled-counter.fileExt.
     *
     * @param {number} counter - used in the name of the new Document's File
     * @param {string} fileExt - file extension of the new Document's File, including "."
     * @return {Document} - a new untitled Document
     */
    function createUntitledDocument(counter, fileExt) {
        var filename = Strings.UNTITLED + "-" + counter + fileExt,
            fullPath = _untitledDocumentPath + "/" + filename,
            now = new Date(),
            file = new InMemoryFile(fullPath, FileSystem);

        return new DocumentModule.Document(file, now, "");
    }

    /**
     * Reacts to a file being deleted: if there is a Document for this file, causes it to dispatch a
     * "deleted" event; ensures it's not the currentDocument; and removes this file from the working
     * set. These actions in turn cause all open editors for this file to close. Discards any unsaved
     * changes - it is expected that the UI has already confirmed with the user before calling.
     *
     * To simply close a main editor when the file hasn't been deleted, use closeFullEditor() or FILE_CLOSE.
     *
     * FUTURE: Instead of an explicit notify, we should eventually listen for deletion events on some
     * sort of "project file model," making this just a private event handler.
     *
     * @param {!File} file
     * @param {boolean} skipAutoSelect - if true, don't automatically open/select the next document
     */
    function notifyFileDeleted(file, skipAutoSelect) {
        // First ensure it's not currentDocument, and remove from working set
        closeFullEditor(file, skipAutoSelect);

        // Notify all other editors to close as well
        var doc = getOpenDocumentForPath(file.fullPath);
        if (doc) {
            $(doc).triggerHandler("deleted");
        }

        // At this point, all those other views SHOULD have released the Doc
        if (doc && doc._refCount > 0) {
            console.log("WARNING: deleted Document still has " + doc._refCount + " references. Did someone addRef() without listening for 'deleted'?");
        }
    }

    /**
     * @private
     * Preferences callback. Saves the state of the working set.
     */
    function _savePreferences() {
        // save the working set file paths
        var files       = [],
            isActive    = false,
            workingSet  = getWorkingSet(),
            currentDoc  = getCurrentDocument(),
            projectRoot = ProjectManager.getProjectRoot();

        if (!projectRoot) {
            return;
        }

        workingSet.forEach(function (file, index) {
            // Do not persist untitled document paths
            if (!(file instanceof InMemoryFile)) {
                // flag the currently active editor
                isActive = currentDoc && (file.fullPath === currentDoc.file.fullPath);

                // save editor UI state for just the working set
                var viewState = EditorManager._getViewState(file.fullPath);

                files.push({
                    file: file.fullPath,
                    active: isActive,
                    viewState: viewState
                });
            }
        });

        // append file root to make file list unique for each project
        _prefs.setValue("files_" + projectRoot.fullPath, files);
    }

    /**
     * @private
     * Initializes the working set.
     */
    function _projectOpen(e) {
        // file root is appended for each project
        var projectRoot = ProjectManager.getProjectRoot(),
            files = _prefs.getValue("files_" + projectRoot.fullPath);

        console.assert(Object.keys(_openDocuments).length === 0);  // no files leftover from prev proj

        if (!files) {
            return;
        }

        var filesToOpen = [],
            viewStates = {},
            activeFile;

        // Add all files to the working set without verifying that
        // they still exist on disk (for faster project switching)
        files.forEach(function (value, index) {
            filesToOpen.push(FileSystem.getFileForPath(value.file));
            if (value.active) {
                activeFile = value.file;
            }
            if (value.viewState) {
                viewStates[value.file] = value.viewState;
            }
        });
        addListToWorkingSet(filesToOpen);

        // Allow for restoring saved editor UI state
        EditorManager._resetViewStates(viewStates);

        // Initialize the active editor
        if (!activeFile && _workingSet.length > 0) {
            activeFile = _workingSet[0].fullPath;
        }

        if (activeFile) {
            var promise = CommandManager.execute(Commands.FILE_OPEN, { fullPath: activeFile });
            // Add this promise to the event's promises to signal that this handler isn't done yet
            e.promises.push(promise);
        }
    }

    /**
     * Called after a file or folder name has changed. This function is responsible
     * for updating underlying model data and notifying all views of the change.
     *
     * @param {string} oldName The old name of the file/folder
     * @param {string} newName The new name of the file/folder
     * @param {boolean} isFolder True if path is a folder; False if it is a file.
     */
    function notifyPathNameChanged(oldName, newName, isFolder) {
        // Notify all open documents
        _.forEach(_openDocuments, function (doc, id) {
            // TODO: Only notify affected documents? For now _notifyFilePathChange
            // just updates the language if the extension changed, so it's fine
            // to call for all open docs.
            doc._notifyFilePathChanged();
        });

        // Send a "fileNameChanged" event. This will trigger the views to update.
        $(exports).triggerHandler("fileNameChange", [oldName, newName]);
    }

    /**
     * Called after a file or folder has been deleted. This function is responsible
     * for updating underlying model data and notifying all views of the change.
     *
     * @param {string} path The path of the file/folder that has been deleted
     */
    function notifyPathDeleted(path) {
        /* FileSyncManager.syncOpenDocuments() does all the work of closing files
           in the working set and notifying the user of any unsaved changes. */
        FileSyncManager.syncOpenDocuments(Strings.FILE_DELETED_TITLE);

        // Send a "pathDeleted" event. This will trigger the views to update.
        $(exports).triggerHandler("pathDeleted", path);
    }

    /**
     * @private
     * Update document
     */
    function _handleLanguageAdded(event, language) {
        _.forEach(_openDocuments, function (doc, key) {
            // No need to look at the new language if this document has one already
            if (doc.getLanguage().isFallbackLanguage()) {
                doc._updateLanguage();
            }
        });
    }

    /**
     * @private
     * Update document
     */
    function _handleLanguageModified(event, language) {
        _.forEach(_openDocuments, function (doc, key) {
            var docLanguage = doc.getLanguage();
            // A modified language can affect a document
            // - if its language was modified
            // - if the document doesn't have a language yet and its file extension was added to the modified language
            if (docLanguage === language || docLanguage.isFallbackLanguage()) {
                doc._updateLanguage();
            }
        });
    }

    // For compatibility
    $(DocumentModule)
        .on("_afterDocumentCreate", function (event, doc) {
            if (_openDocuments[doc.file.id]) {
                console.error("Document for this path already in _openDocuments!");
                return true;
            }

            _openDocuments[doc.file.id] = doc;
            $(exports).triggerHandler("afterDocumentCreate", doc);
        })
        .on("_beforeDocumentDelete", function (event, doc) {
            if (!_openDocuments[doc.file.id]) {
                console.error("Document with references was not in _openDocuments!");
                return true;
            }

            $(exports).triggerHandler("beforeDocumentDelete", doc);
            delete _openDocuments[doc.file.id];
        })
        .on("_documentRefreshed", function (event, doc) {
            $(exports).triggerHandler("documentRefreshed", doc);
        })
        .on("_dirtyFlagChange", function (event, doc) {
            $(exports).triggerHandler("dirtyFlagChange", doc);
            if (doc.isDirty) {
                addToWorkingSet(doc.file);
            }
        })
        .on("_documentSaved", function (event, doc) {
            $(exports).triggerHandler("documentSaved", doc);
        });

    // For unit tests and internal use only
    exports._clearCurrentDocument       = _clearCurrentDocument;

    // Define public API
    exports.Document                    = DocumentModule.Document;
    exports.getCurrentDocument          = getCurrentDocument;
    exports._clearCurrentDocument        = _clearCurrentDocument;
    exports.getDocumentForPath          = getDocumentForPath;
    exports.getOpenDocumentForPath      = getOpenDocumentForPath;
    exports.getDocumentText             = getDocumentText;
    exports.createUntitledDocument      = createUntitledDocument;
    exports.getWorkingSet               = getWorkingSet;
    exports.findInWorkingSet            = findInWorkingSet;
    exports.findInWorkingSetAddedOrder  = findInWorkingSetAddedOrder;
    exports.getAllOpenDocuments         = getAllOpenDocuments;
    exports.setCurrentDocument          = setCurrentDocument;
    exports.addToWorkingSet             = addToWorkingSet;
    exports.addListToWorkingSet         = addListToWorkingSet;
    exports.removeFromWorkingSet        = removeFromWorkingSet;
    exports.removeListFromWorkingSet    = removeListFromWorkingSet;
    exports.getNextPrevFile             = getNextPrevFile;
    exports.swapWorkingSetIndexes       = swapWorkingSetIndexes;
    exports.sortWorkingSet              = sortWorkingSet;
    exports.beginDocumentNavigation     = beginDocumentNavigation;
    exports.finalizeDocumentNavigation  = finalizeDocumentNavigation;
    exports.closeFullEditor             = closeFullEditor;
    exports.closeAll                    = closeAll;
    exports.notifyFileDeleted           = notifyFileDeleted;
    exports.notifyPathNameChanged       = notifyPathNameChanged;
    exports.notifyPathDeleted           = notifyPathDeleted;

    // Setup preferences
    _prefs = PreferencesManager.getPreferenceStorage(module);

    // Performance measurements
    PerfUtils.createPerfMeasurement("DOCUMENT_MANAGER_GET_DOCUMENT_FOR_PATH", "DocumentManager.getDocumentForPath()");

    // Handle project change events
    var $ProjectManager = $(ProjectManager);
    $ProjectManager.on("projectOpen", _projectOpen);
    $ProjectManager.on("beforeProjectClose beforeAppClose", _savePreferences);

    // Handle Language change events
    $(LanguageManager).on("languageAdded", _handleLanguageAdded);
    $(LanguageManager).on("languageModified", _handleLanguageModified);
});


language: node_js
node_js:
  - "0.8"
  - "0.10"


/* basic tests */
article.content {
    flow-into: main;
}

section.layout > div {
    flow-from: main;
}

#jeff.content {
    flow-into: jeff;
}

#jeff.layout > div {
    flow-from: jeff;
}

/* exclude matches inside comments tests */

/*
p.content {
    flow-into: carter;
}

p.layout > div {
    flow-from: carter;
}
*/

/* exclude matches inside strings tests */

div {
    content: "/* p.content { flow-into: carter; } p.layout > div { flow-from: carter; } */";
}

div {
    content: "html.content { flow-into: dexter; } html.layout > div { flow-from: dexter; }";
}

/*
div.content {
    content: "flow-into: martin;";
}

div.layout > div {
    content: "flow-from: martin;";
}
*/

/* multi-line property tests */

#randy.content {
    flow-into:
        randy
        ;
}

#randy.layout > div {
    flow-from:  randy;
}

/* test to exclude duplicates */
#yin.content {
    flow-from: ;
    flow-into: jeff;
}

#yin.layout > div {
    flow-from: jeff;
}

/* flow-from only tests */
#raymond.layout > div {
    flow-from: lim;

}

/* underscores and dashes */
#ingo.content {
    flow-from: edge-code_now_shipping;
}

/* invalid / ignored flows */
#test364.content {
    flow-from: default;
    flow-from: none;
    flow-from: inherit;
    flow-from: auto;
    flow-from: initial;
    flow-from: content;
    flow-from: element;
}


displayPath += '<span class="quicksearch-match">';

displayName += '<span class="quicksearch-match">';

keep keep keep keep replace replace keep keep keep keep

displayPath += '<span class="quicksearch-pathmatch">';

displayName += '<span class="quicksearch-namematch">';

/*
 * 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.
 *
 */

/*
 * This file first imports all of the shared LESS/CSS, and then defines
 * version-specific layout (e.g. layout for the App shell, or layout for
 * the In-Browser version on Firefox).
 *
 * Going forward, we may have different versions of this file for different
 * Versions (e.g. App, In-Browser, etc.)
 *
 * Anything that is general/reusable should be pushed up into a LESS file
 * in the "styles" directory. See "brackets_shared.less" for the organization
 *
 * Date: @DATE
 */

/* IMPORTANT: importing brackets_shared should be the first step in this file
 * This is so that imported CSS files (NOT LESS files) end up in the
 * right place
 */
@import "brackets_shared.less";

/* Overall layout */

html, body {
    height: 100%;
    overflow: hidden;

    /* Turn off selection for UI elements */
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -o-user-select: none;
    user-select: none;

    /* And make sure we get a pointer cursor even over text */
    cursor: default;
}

body {
    height: 100%;
}

.resizing-container {
    position: absolute;
    top: 0;
    width: 100%;
    height: 100%;
    z-index: @z-index-brackets-panel-resizer;

    &.horz-resizing {
        cursor: col-resize;
    }

    &.vert-resizing {
        cursor: row-resize;
    }
}

a, img {
    -webkit-user-drag: none;
}

.main-view {
    height: 100%;

    .sidebar {
        height: 100%;
        .vbox;
        width: @sidebar-width;
        float: left;
    }

    .content {
        height: 100%;
    }
}

.toolbar {
    /* make sure the shadow goes above other items */
    z-index: @z-index-brackets-toolbar;
}

.busyCursor {
    cursor: wait !important;
}

#status-bar {
    position: relative;
    background-color: @background-color-3;
    border-top: 1px solid darken(@background-color-3, @bc-color-step-size / 2);
    box-sizing: border-box;
    color: @bc-grey;
    font-family: @fontstack-sans-serif;
    font-size: 0.9em;
    line-height: 1;

    height: 20px;
    overflow: hidden;
    padding: 3px 20px 3px 10px;
}

#status-info {
    position: absolute;
    left: 8px;
    background-color: @background-color-3;
    white-space: nowrap;

    div {
        display: inline;
        margin-right: 20px;
    }
}

#status-indicators {
    position: absolute;
    right: 4px;
    background-color: @background-color-3;
    white-space: nowrap;

    &>div {
        display: inline;
        margin-left: 20px;

        &#busy-indicator {
            display: inline-block;
            vertical-align: middle;
            margin-top: -2px;
            width: 12px;
            height: 12px;
            background: url("images/small_spinner.svg") no-repeat;
            visibility: hidden;

            &.spin {
                -webkit-animation: rotating 1.5s linear infinite;
                visibility: visible;
            }
        }
    }
}

@-webkit-keyframes rotating {
    0% {
        -webkit-transform: rotate(0deg);
    }

    100% {
        -webkit-transform: rotate(360deg);
    }
}

#status-indent > * {
    display: inline-block;
}

#status-indent > *.hidden {
    display: none;
}

#indent-type, #indent-width-label {
    margin-right: 2px;
}

#indent-type, #indent-width-label {
    cursor: pointer;
}

#indent-width-label {
    width: 14px;
}

#indent-type:hover, #indent-width-label:hover {
    text-decoration: underline;
}

#indent-width-input {
    font-size: 1em;
    height: 12px;
    line-height: 1;
    vertical-align: -2px;
    border: 1px solid darken(@background-color-3, @bc-color-step-size / 2);
    color: @bc-black;
    margin: 0;
    padding: 0;
    width: 14px;
    position: relative;
    left: -1px;
    top: -2px;
    -webkit-border-radius: 2px;
    border-radius: 2px;
    box-shadow: inset 0 1px 0 rgba(0,0,0,0.1);
}

#indent-width-input:focus {
    border: 1px solid rgba(9,64,253,0.84);
    outline: none;
}

#indent-width-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

#editor-holder {
    position: relative;

    /* Placeholder shown when there is no editor open */
    #not-editor {
        height: 100%;
        .vbox;
        .box-pack(center);
        .box-align(center);
        background: @background-color-2 url('images/no_content_bg.svg') no-repeat center 45%;
    }
}

.vert-resizer {
    position: absolute;
    height: 6px;
    width: 100%;
    z-index: @z-index-brackets-panel-resizer;
    opacity: 0;
    cursor: row-resize;
}

.horz-resizer {
    position: absolute;
    height: 100%;
    width: 6px;
    z-index: @z-index-brackets-panel-resizer;
    opacity: 0;
    cursor: col-resize;
}

.bottom-panel {
    display: none;
    height: 200px;
    border-top-style: solid;
    border-width: 1px;
    border-color: lighten(@bc-grey, @bc-color-step-size*4);

    .toolbar {
        height: auto;
        padding-top: @base-padding / 2;
        padding-bottom: @base-padding / 2;
        z-index: @z-index-brackets-results-panel;
        .box-shadow(0 -1px 3px 0 fadeout(@bc-black, 70%));
        .title {
            font-size: @label-font-size;
            font-weight: bold;
        }
        .close {
            position: absolute;
            right: 10px;
        }
    }

    .table-container {
        height: 170px;
        overflow: auto;

        td {
            padding-left: @base-padding;
            padding-right: @base-padding;
        }

        .highlight {
            background: #d2dcf8;
        }

        tr.selected td {
            background-color: @bc-light-blue;
        }
    }
}

#update-notification {
    padding-right: 5px;
    margin-bottom: -1px;
    .sprite-icon(0,0, 14px,14px, "images/updateSprites.svg");
    &:hover {
        .sprite-swap(0,14px);
    }
    display:none;
}

.jslint-disabled {
    font-size: 1em;
    color: @bc-grey;
}

.jslint-errors {
    font-size: 1em;
    color: @bc-red;
}

.jslint-valid {
    font-size: 1em;
    color: lighten(@bc-yellow, @bc-color-step-size*2);
}

#toolbar-go-live {
    padding-right: 8px;

    // Default icon is for the 'disconnected' state
    // The 'connecting failed' (.warning) state also maps here
    .sprite-icon(0,0, 22px,11px, "images/live_development_sprites.svg");
    &:hover {
        .sprite-swap(0,11px);
    }
    // 'Connected' state
    &.success {
        .sprite-swap(0,22px);
    }
    &.success:hover {
        .sprite-swap(0,33px);
    }
    // 'Connection in progress' state
    &.info {
        .sprite-swap(0,44px);
    }
    // 'Out-of-sync' state
    &.out-of-sync {
        .sprite-swap(0,55px);
    }
    &.out-of-sync:hover {
        .sprite-swap(0,66px);
    }
}

/* Project panel */

#project-header {
    /* Fixed height blank space for now. The mockups have the window "stoplights" or a logo here. */
    height: 29px;
}

#sidebar-resizer {
    position: absolute;
    width: 6px;
    height: 100%;
    z-index: @z-index-brackets-sidebar-resizer;
    opacity: 0;
    cursor: col-resize;
}

#file-section {
    .vbox;
    .box-flex(1);
    margin: 5px 0px 0px 0px;
    position: relative;

    .project-file-header-area {
        padding: 6px 26px 2px 10px;
        font-size: 13px;
        color: #929292;

        // extra right padding is for this icon
        .settings {
            position: absolute;
            right: 8px;
            margin-top: 3px;
            .sprite-icon(0, 0, 14px, 14px, "images/settings_small.svg");

            &:hover {
                .sprite-swap(0, 14px);
            }
            &:active {
                .sprite-swap(0, 28px);
            }
        }
    }

    .file-list-divider {
        border-bottom: 1px solid #9a9a9a;
        box-shadow: 0px 1px #eeeeee;
    }
}

#open-files-container {
    .box-flex(0);
    margin: 0 0 22px 0;
    padding: 0px;
    max-height: 200px; // TODO (Issue #276): it would be nicer to have this be 50%, but that doesn't seem to work

    ul {
        list-style-type: none;
        margin: 0;
    }

    li {
        white-space: nowrap;
        position: relative; // so that children can be positioned absolute
        line-height: 18px;
        padding: 0 0 0 8px;
        min-height: 18px;
        vertical-align: baseline;
    }

    a {
        color: #BBB;

        font-size: 13px;
        text-decoration: none;

        display: block;
        height: 16px;
        line-height: 16px;
        margin-left: 18px;
        padding: 3px (@triangle-size * 2) 3px 0;

        cursor: default;
    }

    .selected a {
        color: #fff;
    }
}

.sidebar-selection {
    background: url("images/active_back.png") no-repeat top right;
    height: 22px;
    position: absolute;
}

.sidebar-selection-triangle {
    background: url("images/active_back.png");
    width: 10px;
    height: 22px;
    position: fixed;

    z-index: @z-index-brackets-selection-triangle; /* scroller-shadow appears above this triangle */
}

.sidebar-selection-triangle.triangle-visible:before {
    content: "";

    border-top: @triangle-size solid transparent;
    border-bottom: @triangle-size solid transparent;
    border-right: @triangle-size solid @background-color-3;

    display: block;
    position: absolute;
    top: 2px;
    right: 0;
    width: 0;
    height: 0;

    .scale-x(0.9, right, top);
}

//Initially start with the open files hidden, they will get show as files are added
#open-files-container {
    display:none;
}

#project-files-container {
    .box-flex(1);

    .jstree-brackets li > a {
        padding-right: @triangle-size * 2;
    }

    ul {
        padding-left: 8px;
    }
}

#editor-holder .scroller-shadow {
    width: 100%;
}

.scroller-shadow {
    background-size: 100%;
    background-repeat: no-repeat;
    height: 5px;
    position: fixed;
    z-index: @z-index-brackets-scroller-shadow;

    &.top {
        #gradient.vertical(rgba(0,0,0,0.1), rgba(0,0,0,0));
        background-position: 0 -5px;
    }

    &.bottom {
        #gradient.vertical(rgba(0,0,0,0), rgba(0,0,0,0.1));
        background-position: 0 5px;
        background-color: transparent; /* override background-color: @endColor from #gradient.vertical */
    }
}

@sprite-size-18: 4px;

/** Classes for icons from jsTreeSprites.svg
*/
.jstree-sprite {
    background-image: url("images/jsTreeSprites.svg");
    background-repeat: no-repeat;
    background-color: transparent;
    vertical-align: middle;
    width: @sprite-size-18;
    height: @sprite-size-18;
}

.disclosure-arrow-opened {
    .jstree-sprite;
    display: inline-block;
    background-position: -@sprite-size-18 0px;
}

.disclosure-arrow-closed {
    .jstree-sprite;
    display: inline-block;
    background-position: 0px 0px;
}
ins.jstree-icon {
    left: -12px !important;
}
/** Classes for icons from bracketSprites.png
*/

.bracket-sprite {
    background-image: url("images/close_btn.svg");
    background-repeat: no-repeat;
    background-color: transparent;
    vertical-align: middle;
    width: 15px;
    height: 16px;
}

.file-status-icon {
    margin: 3px 0 0 8px;
    .bracket-sprite;
    display: inline-block;
    position: absolute;
    left: 0px;
    top: 1px;

    &.dirty {
        background-position: -30px 0;
    }

    &.can-close {
        background-position: 0 0;
    }

    &.can-close:hover {
        background-position: -15px 0;
    }

    &.can-close:active {
        background-position: -15px 1px;
    }
}

/* Styles for inline editors */
.inline-widget {
    background-color:   @inline-background-color-1;

    .inline-editor-header {
        padding: 10px 10px 0px 10px;

        .filename {
            font-family: @fontstack-sans-serif;
            font-size: 1.1em;
            color: @inline-color-1;

            // Filename header is clickable (it's an <a> tag, so we get underscore on hover by
            // default; but the hand cursor is shut off by Bootstrap's reset stylesheet)
            cursor: pointer;

            .dirty-indicator {
                .bracket-sprite;
                display: inline-block;
                background-position: -32px 0;
                padding-top: 3px;
            }

            .line-number {
                color: @inline-color-2;
            }
        }
    }

    .shadow {
        display: block;
        height: 4px;
        width: 100%;
        position: absolute;
        content: " ";
        left: 0;
        z-index: @z-index-brackets-inline-editor-shadow;
    }

    .shadow.top {
        top: 0px;
        background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0));
    }

    .shadow.bottom {
        bottom: 0px;
        background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1));
    }

    .CodeMirror-scroll {
        /* Don't show horizontal scrollbars within an inline editor--we want to scroll the outer editor */
        overflow-x: hidden;
        background-color: transparent;
    }
}

/* CSSInlineEditor rule list */
.related-container {
    @top-margin: 12px;

    /*width: 0;*/
    font-family: @fontstack-sans-serif;
    position: fixed;
    width: 250px;
    overflow: hidden;
    /*-webkit-transition: width 0.15s ease-out;*/
    background: @inline-background-color-2;

    .selection {
        width: 100%;
        background: #d0d5d5;
        position: absolute;
        -webkit-transition: height 0.1s ease-out;
        -webkit-transition: top 0.1s ease-out;
        border-top: 1px solid darken(@inline-background-color-3, @bc-color-step-size);
        border-bottom: 1px solid lighten(@inline-background-color-3, @bc-color-step-size);
        top: @top-margin;
    }

    /*
     * CSS triangle hack with anti-alias workarounds:
     * (a) Use selection-background-color instead of transparent.
     * (b) Use transform scaleX and origin to adjust width.
     */
    .selection:before {
        content: " ";
        position: absolute;
        width: 0;
        height: 0;
        border-top: @triangle-size solid @inline-background-color-3;
        border-bottom: @triangle-size solid @inline-background-color-3;
        border-left: @triangle-size solid @inline-background-color-1;
        margin-top: -@triangle-size;
        top: 50%;
        .scale-x(0.9, left, top);
    }

    .related {
        font-size: 12px;
        position: absolute;
        top: 0;
        left: 1px;
        width: 100%;

        ul {
            margin: 0;
            padding: @top-margin 0px;
            list-style: none;
        }

        li {
            color: @inline-color-2;
            margin: 0;
            overflow: hidden;
            padding: 2px 0px 2px 15px;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        .selected {
            color: @inline-color-3;
            -webkit-transition: color 0.1s ease-out .15s;
        }
    }
}

/* This text is used to force the code editor's font to be loaded early on if it's a
   web font. This is necessary in order for the editor's horizontal measurement of text
   to work properly.

   In the future, when we allow the user to switch fonts, we'll need to make sure to
   update the font for this text item whenever the user switches. We'll also need to
   wait to initiate a re-measure in CodeMirror until the new font is loaded (if they
   choose a web font). This library allows for checking if a web font is loaded:

     http://code.google.com/apis/webfonts/docs/webfont_loader.html
*/
.dummy-text {
    position: fixed;
    top: -10000px;
    .code-font();
}

/* Find & Replace search bars - temporary UI, to be replaced with a richer search feature later */

.CodeMirror-dialog {
    position: relative;
    z-index: @z-index-cm-dialog-override;
}

.CodeMirror-dialog > div {
    font-family: @fontstack-sans-serif;
    position: absolute;
    top: 0; left: 0; right: 0;
    background: @background-color-2;
    color: @content-color;
    border-bottom: 1px solid @bc-gray;
    .box-shadow(0 1px 3px 0 fadeout(@bc-black, 70%));
    z-index: 1;
    padding: .5em .8em;
    overflow: hidden;
}

.CodeMirror-dialog input {
    font-family: @fontstack-sans-serif;
    border: 1px solid @content-color-weaker;
    outline: none;
    background: @background-color-3;
    width: 20em;
    margin: .3em .3em;
    color: inherit;
    &.no-results {
        background-color: mix(@background-color-3, #FF0000, 70%);
    }
}

.CodeMirror-searching {
    background-color: inherit;
}

/* Quick Open search bar & dropdown */

.smart_autocomplete_container {

    border: 1px solid #999;
    background-color: #FFFFFF;

    /* smart auto complete doesn't correctly position the container
     * so these specific padding and margin values are necessary*/
    padding: 0px 8px 0px 0px;
    margin: 9px 0px 0px 0px;

    .quick-open-path {
        font-size: 11px;
        color: gray;
    }

    li {
        color: black;
        border-bottom: 1px solid;
        list-style: none;
        cursor: pointer;
        padding: 0px 2px 0px 4px;
    }
}

li.smart_autocomplete_highlight {
    background-color: #e0f0fa;
}

.clickable-link {
    cursor: pointer;
}

.quicksearch-match {
    font-weight: bold;
    color: #555;
}


beforeEach(function() {
  this.addMatchers({
    toBePlaying: function(expectedSong) {
      var player = this.actual;
      return player.currentlyPlayingSong === expectedSong &&
             player.isPlaying;
    }
  });
});


<!doctype html>

	<meta charset="utf-8">
	<title>jQuery UI Droppable Test Suite</title>

	<link rel="stylesheet" href="../../../external/qunit.css">

jQuery UI Droppable Test Suite

Draggable

Droppable


/* Estonian initialisation for the jQuery UI date picker plugin. */
/* Written by Mart Sõmermaa (mrts.pydev at gmail com). */
jQuery(function($){
	$.datepicker.regional['et'] = {
		closeText: 'Sulge',
		prevText: 'Eelnev',
		nextText: 'Järgnev',
		currentText: 'Täna',
		monthNames: ['Jaanuar','Veebruar','Märts','Aprill','Mai','Juuni',
		'Juuli','August','September','Oktoober','November','Detsember'],
		monthNamesShort: ['Jaan', 'Veebr', 'Märts', 'Apr', 'Mai', 'Juuni',
		'Juuli', 'Aug', 'Sept', 'Okt', 'Nov', 'Dets'],
		dayNames: ['Pühapäev', 'Esmaspäev', 'Teisipäev', 'Kolmapäev', 'Neljapäev', 'Reede', 'Laupäev'],
		dayNamesShort: ['Pühap', 'Esmasp', 'Teisip', 'Kolmap', 'Neljap', 'Reede', 'Laup'],
		dayNamesMin: ['P','E','T','K','N','R','L'],
		weekHeader: 'näd',
		dateFormat: 'dd.mm.yy',
		firstDay: 1,
		isRTL: false,
		showMonthAfterYear: false,
		yearSuffix: ''};
	$.datepicker.setDefaults($.datepicker.regional['et']);
});


.clickable-link {
cursor: pointer;
}
.quicksearch-match {
font-weight: bold;
color: #555;
}

keep keep keep keep keep replace keep keep keep


/* Polish initialisation for the jQuery UI date picker plugin. */
/* Written by Jacek Wysocki (jacek.wysocki@gmail.com). */
jQuery(function($){
	$.datepicker.regional['pl'] = {
		closeText: 'Zamknij',
		prevText: '<Poprzedni',
		nextText: 'Następny>',
		currentText: 'Dziś',
		monthNames: ['Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec',
		'Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'],
		monthNamesShort: ['Sty','Lu','Mar','Kw','Maj','Cze',
		'Lip','Sie','Wrz','Pa','Lis','Gru'],
		dayNames: ['Niedziela','Poniedziałek','Wtorek','Środa','Czwartek','Piątek','Sobota'],
		dayNamesShort: ['Nie','Pn','Wt','Śr','Czw','Pt','So'],
		dayNamesMin: ['N','Pn','Wt','Śr','Cz','Pt','So'],
		weekHeader: 'Tydz',
		dateFormat: 'dd.mm.yy',
		firstDay: 1,
		isRTL: false,
		showMonthAfterYear: false,
		yearSuffix: ''};
	$.datepicker.setDefaults($.datepicker.regional['pl']);
});


<!doctype html>

	<meta charset="utf-8">
	<title>Button Visual Test</title>
	<link rel="stylesheet" href="../../../themes/base/jquery.ui.all.css">

	<button>button</button>
	<button>button</button>
	<button>button</button>

	<button type="button">button button</button>
	<button type="button">button button</button>
	<button type="button">button button</button>

	<button type="submit">button submit</button>
	<button type="submit">button submit</button>
	<button type="submit">button submit</button>

	<input type="button" value="input button">
	<input type="button" value="input button">
	<input type="button" value="input button">

	<input type="submit" value="input submit">
	<input type="submit" value="input submit">
	<input type="submit" value="input submit">

	<input type="checkbox" id="input-checkbox0">
	<input type="checkbox" id="input-checkbox1">
	<input type="checkbox" id="input-checkbox2">
	input checkbox
	input checkbox
	input checkbox

	<input type="radio" id="input-radio0" name="radio">
	<input type="radio" id="input-radio1" name="radio">
	<input type="radio" id="input-radio2" name="radio">
	input radio
	input radio
	input radio

	anchor
	anchor
	anchor


.quicksearch-namematch {
font-weight: bold;
}


<!doctype html>

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

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

	Select a value:
	<input id="spinner" name="value" />

Overflowing spinner restricted to a range of -10 to 10.
For anything above 10, it'll overflow to -10, and the other way round.


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

var log = TestHelpers.menu.log,
	click = TestHelpers.menu.click;

module("menu: events");

test("handle click on menu", function() {
	expect(1);
	var menu = $('#menu1').menu({
		select: function(event, ui) {
			log();
		}
	});
	log("click",true);
	click($('#menu1'),"1");
	log("afterclick");
	click( menu,"2");
	click($('#menu1'),"3");
	click( menu,"1");
	equal( $("#log").html(), "1,3,2,afterclick,1,click,", "Click order not valid.");
});

test("handle click on custom item menu", function() {
	expect(1);
	var menu = $('#menu5').menu({
		select: function(event, ui) {
			log();
		},
		menus: "div"
	});
	log("click",true);
	click($('#menu5'),"1");
	log("afterclick");
	click( menu,"2");
	click($('#menu5'),"3");
	click( menu,"1");
	equal( $("#log").html(), "1,3,2,afterclick,1,click,", "Click order not valid.");
});

asyncTest( "handle blur", function() {
	expect( 1 );
	var blurHandled = false,
		$menu = $( "#menu1" ).menu({
			blur: function( event, ui ) {
				// Ignore duplicate blur event fired by IE
				if ( !blurHandled ) {
					blurHandled = true;
					equal( event.type, "menublur", "blur event.type is 'menublur'" );
				}
			}
		});

	click( $menu, "1" );
	setTimeout( function() {
		$menu.blur();
		start();
	}, 350);
});

asyncTest( "handle blur on click", function() {
	expect( 1 );
	var blurHandled = false,
		$menu = $( "#menu1" ).menu({
			blur: function( event, ui ) {
				// Ignore duplicate blur event fired by IE
				if ( !blurHandled ) {
					blurHandled = true;
					equal( event.type, "menublur", "blur event.type is 'menublur'" );
				}
			}
		});

	click( $menu, "1" );
	setTimeout( function() {
		$( "<a>", { id: "remove"} ).appendTo("body").trigger( "click" );
		$("#remove").remove();
		start();
	}, 350);
});

test( "handle focus of menu with active item", function() {
	expect( 1 );
	var element = $( "#menu1" ).menu({
		focus: function( event, ui ) {
			log( $( event.target ).find( ".ui-state-focus" ).parent().index() );
		}
	});

	log( "focus", true );
	element.focus();
	element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
	element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
	element.focus();
	equal( $("#log").html(), "2,2,1,0,focus,", "current active item remains active");
});

asyncTest( "handle submenu auto collapse: mouseleave", function() {
	expect( 4 );
	var $menu = $( "#menu2" ).menu(),
		event = $.Event( "mouseenter" );

	function menumouseleave1() {
		equal( $menu.find( "ul[aria-expanded='true']" ).length, 1, "first submenu expanded" );
		$menu.menu( "focus", event, $menu.find( "li:nth-child(7) li:first" ) );
		setTimeout( menumouseleave2, 350 );
	}
	function menumouseleave2() {
		equal( $menu.find( "ul[aria-expanded='true']" ).length, 2, "second submenu expanded" );
		$menu.find( "ul[aria-expanded='true']:first" ).trigger( "mouseleave" );
		setTimeout( menumouseleave3, 350 );
	}
	function menumouseleave3() {
		equal( $menu.find( "ul[aria-expanded='true']" ).length, 1, "second submenu collapsed" );
		$menu.trigger( "mouseleave" );
		setTimeout( menumouseleave4, 350 );
	}
	function menumouseleave4() {
		equal( $menu.find( "ul[aria-expanded='true']" ).length, 0, "first submenu collapsed" );
		start();
	}

	$menu.find( "li:nth-child(7)" ).trigger( "mouseenter" );
	setTimeout( menumouseleave1, 350 );
});

asyncTest( "handle submenu auto collapse: mouseleave", function() {
	expect( 4 );
	var $menu = $( "#menu5" ).menu( { menus: "div" } ),
		event = $.Event( "mouseenter" );

	function menumouseleave1() {
		equal( $menu.find( "div[aria-expanded='true']" ).length, 1, "first submenu expanded" );
		$menu.menu( "focus", event, $menu.find( ":nth-child(7)" ).find( "div" ).eq( 0 ).children().eq( 0 ) );
		setTimeout( menumouseleave2, 350 );
	}
	function menumouseleave2() {
		equal( $menu.find( "div[aria-expanded='true']" ).length, 2, "second submenu expanded" );
		$menu.find( "div[aria-expanded='true']:first" ).trigger( "mouseleave" );
		setTimeout( menumouseleave3, 350 );
	}
	function menumouseleave3() {
		equal( $menu.find( "div[aria-expanded='true']" ).length, 1, "second submenu collapsed" );
		$menu.trigger( "mouseleave" );
		setTimeout( menumouseleave4, 350 );
	}
	function menumouseleave4() {
		equal( $menu.find( "div[aria-expanded='true']" ).length, 0, "first submenu collapsed" );
		start();
	}

	$menu.find( ":nth-child(7)" ).trigger( "mouseenter" );
	setTimeout( menumouseleave1, 350 );

});

test("handle keyboard navigation on menu without scroll and without submenus", function() {
	expect(12);
	var element = $('#menu1').menu({
		select: function(event, ui) {
			log($(ui.item[0]).text());
		},
		focus: function( event, ui ) {
			log($(event.target).find(".ui-state-focus").parent().index());
		}
	});

	log("keydown",true);
	element.focus();
	element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
	element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
	equal( $("#log").html(), "2,1,0,keydown,", "Keydown DOWN");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
	equal( $("#log").html(), "1,keydown,", "Keydown UP");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
	equal( $("#log").html(), "keydown,", "Keydown LEFT (no effect)");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
	equal( $("#log").html(), "keydown,", "Keydown RIGHT (no effect)");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
	equal( $("#log").html(), "4,keydown,", "Keydown PAGE_DOWN");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
	equal( $("#log").html(), "keydown,", "Keydown PAGE_DOWN (no effect)");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
	equal( $("#log").html(), "0,keydown,", "Keydown PAGE_UP");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
	equal( $("#log").html(), "keydown,", "Keydown PAGE_UP (no effect)");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.END } );
	equal( $("#log").html(), "4,keydown,", "Keydown END");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.HOME } );
	equal( $("#log").html(), "0,keydown,", "Keydown HOME");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } );
	equal( $("#log").html(), "keydown,", "Keydown ESCAPE (no effect)");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
	equal( $("#log").html(), "Aberdeen,keydown,", "Keydown ENTER");
});

asyncTest("handle keyboard navigation on menu without scroll and with submenus", function() {
	expect(16);
	var element = $('#menu2').menu({
		select: function(event, ui) {
			log($(ui.item[0]).text());
		},
		focus: function( event, ui ) {
			log($(event.target).find(".ui-state-focus").parent().index());
		}
	});

	log("keydown",true);
	element.one( "menufocus", function( event, ui ) {
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		equal( $("#log").html(), "2,1,keydown,", "Keydown DOWN");
		setTimeout( menukeyboard1, 50 );
	});
	element.focus();

	function menukeyboard1() {
		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
		equal( $("#log").html(), "0,1,keydown,", "Keydown UP");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
		equal( $("#log").html(), "keydown,", "Keydown LEFT (no effect)");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );

		setTimeout( function() {
			equal( $("#log").html(), "0,4,3,2,1,keydown,", "Keydown RIGHT (open submenu)");
		}, 50);
		setTimeout( menukeyboard2, 50 );
	}

	function menukeyboard2() {
		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
		equal( $("#log").html(), "4,keydown,", "Keydown LEFT (close submenu)");

		//re-open submenu
		element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
		setTimeout( menukeyboard3, 50 );
	}

	function menukeyboard3() {
		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
		equal( $("#log").html(), "2,keydown,", "Keydown PAGE_DOWN");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
		equal( $("#log").html(), "keydown,", "Keydown PAGE_DOWN (no effect)");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
		equal( $("#log").html(), "0,keydown,", "Keydown PAGE_UP");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
		equal( $("#log").html(), "keydown,", "Keydown PAGE_UP (no effect)");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.END } );
		equal( $("#log").html(), "2,keydown,", "Keydown END");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.HOME } );
		equal( $("#log").html(), "0,keydown,", "Keydown HOME");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } );
		equal( $("#log").html(), "4,keydown,", "Keydown ESCAPE (close submenu)");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.SPACE } );
		setTimeout( menukeyboard4, 50 );
	}

	function menukeyboard4() {
		equal( $("#log").html(), "0,keydown,", "Keydown SPACE (open submenu)");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } );
		equal( $("#log").html(), "4,keydown,", "Keydown ESCAPE (close submenu)");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
		setTimeout( function() {
			element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
			element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
			setTimeout( function() {
				element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
				element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
				element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
				equal( $("#log").html(), "0,4,2,0,1,0,6,5,keydown,", "Keydown skip dividers and items without anchors");

				log("keydown",true);
				element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
				setTimeout( menukeyboard6, 50 );
			}, 50 );
		}, 50 );
	}

	function menukeyboard6() {
		equal( $("#log").html(), "Ada,keydown,", "Keydown ENTER (open submenu)");

		start();
	}
});

test("handle keyboard navigation on menu with scroll and without submenus", function() {
	expect(14);
	var element = $('#menu3').menu({
		select: function(event, ui) {
			log($(ui.item[0]).text());
		},
		focus: function( event, ui ) {
			log($(event.target).find(".ui-state-focus").parent().index());
		}
	});

	log("keydown",true);
	element.focus();
	element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
	element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
	equal( $("#log").html(), "2,1,0,keydown,", "Keydown DOWN");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
	element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
	equal( $("#log").html(), "0,1,keydown,", "Keydown UP");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
	equal( $("#log").html(), "keydown,", "Keydown LEFT (no effect)");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
	equal( $("#log").html(), "keydown,", "Keydown RIGHT (no effect)");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
	equal( $("#log").html(), "10,keydown,", "Keydown PAGE_DOWN");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
	equal( $("#log").html(), "20,keydown,", "Keydown PAGE_DOWN");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
	equal( $("#log").html(), "10,keydown,", "Keydown PAGE_UP");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
	equal( $("#log").html(), "0,keydown,", "Keydown PAGE_UP");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
	equal( $("#log").html(), "keydown,", "Keydown PAGE_UP (no effect)");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.END } );
	equal( $("#log").html(), "37,keydown,", "Keydown END");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
	equal( $("#log").html(), "keydown,", "Keydown PAGE_DOWN (no effect)");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.HOME } );
	equal( $("#log").html(), "0,keydown,", "Keydown HOME");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } );
	equal( $("#log").html(), "keydown,", "Keydown ESCAPE (no effect)");

	log("keydown",true);
	element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
	equal( $("#log").html(), "Aberdeen,keydown,", "Keydown ENTER");
});

asyncTest("handle keyboard navigation on menu with scroll and with submenus", function() {
	expect(14);
	var element = $('#menu4').menu({
		select: function(event, ui) {
			log($(ui.item[0]).text());
		},
		focus: function( event, ui ) {
			log($(event.target).find(".ui-state-focus").parent().index());
		}
	});

	log("keydown",true);
	element.one( "menufocus", function( event, ui ) {
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		equal( $("#log").html(), "2,1,keydown,", "Keydown DOWN");
		setTimeout( menukeyboard1, 50 );
	});
	element.focus();

	function menukeyboard1() {
		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } );
		equal( $("#log").html(), "0,1,keydown,", "Keydown UP");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
		equal( $("#log").html(), "keydown,", "Keydown LEFT (no effect)");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );

		setTimeout( function() {
			equal( $("#log").html(), "0,1,keydown,", "Keydown RIGHT (open submenu)");
		}, 50);
		setTimeout( menukeyboard2, 50 );
	}

	function menukeyboard2() {
		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
		equal( $("#log").html(), "1,keydown,", "Keydown LEFT (close submenu)");

		//re-open submenu
		element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );
		setTimeout( menukeyboard3, 50 );
	}

	function menukeyboard3() {
		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
		equal( $("#log").html(), "10,keydown,", "Keydown PAGE_DOWN");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
		equal( $("#log").html(), "20,keydown,", "Keydown PAGE_DOWN");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
		equal( $("#log").html(), "10,keydown,", "Keydown PAGE_UP");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
		equal( $("#log").html(), "0,keydown,", "Keydown PAGE_UP");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.END } );
		equal( $("#log").html(), "27,keydown,", "Keydown END");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.HOME } );
		equal( $("#log").html(), "0,keydown,", "Keydown HOME");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE } );
		equal( $("#log").html(), "1,keydown,", "Keydown ESCAPE (close submenu)");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
		setTimeout( menukeyboard4, 50 );
	}

	function menukeyboard4() {
		equal( $("#log").html(), "0,keydown,", "Keydown ENTER (open submenu)");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
		equal( $("#log").html(), "Aberdeen,keydown,", "Keydown ENTER (select item)");

		start();
	}
});

asyncTest("handle keyboard navigation and mouse click on menu with disabled items", function() {
	expect(6);
	var element = $('#menu6').menu({
		select: function(event, ui) {
			log($(ui.item[0]).text());
		},
		focus: function( event, ui ) {
			log($(event.target).find(".ui-state-focus").parent().index());
		}
	});

	log("keydown",true);
	element.one( "menufocus", function( event, ui ) {
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );
		equal( $("#log").html(), "1,keydown,", "Keydown focus but not select disabled item");
		setTimeout( menukeyboard1, 50 );
	});
	element.focus();

	function menukeyboard1() {
		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } );
		equal( $("#log").html(), "4,3,2,keydown,", "Keydown focus disabled item with submenu");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.LEFT } );
		equal( $("#log").html(), "keydown,", "Keydown LEFT (no effect)");

		log("keydown",true);
		element.simulate( "keydown", { keyCode: $.ui.keyCode.RIGHT } );

		setTimeout( function() {
			equal( $("#log").html(), "keydown,", "Keydown RIGHT (no effect on disabled sub-menu)");

			log("keydown",true);
			element.simulate( "keydown", { keyCode: $.ui.keyCode.ENTER } );

			setTimeout( function() {
				equal( $("#log").html(), "keydown,", "Keydown ENTER (no effect on disabled sub-menu)");
				log("click",true);
				click( element, "1" );
				equal( $("#log").html(), "click,", "Click disabled item (no effect)");
				start();
			}, 50);
		}, 50);
	}
});

test("handle keyboard navigation with spelling of menu items", function() {
	expect( 2 );
	var element = $( "#menu2" ).menu({
		focus: function( event, ui ) {
			log( $( event.target ).find( ".ui-state-focus" ).parent().index() );
		}
	});

	log( "keydown", true );
	element.one( "menufocus", function( event, ui ) {
		element.simulate( "keydown", { keyCode: 65 } );
		element.simulate( "keydown", { keyCode: 68 } );
		element.simulate( "keydown", { keyCode: 68 } );
		equal( $("#log").html(), "3,1,0,keydown,", "Keydown focus Addyston by spelling the first 3 letters");
		element.simulate( "keydown", { keyCode: 68 } );
		equal( $("#log").html(), "4,3,1,0,keydown,", "Keydown focus Delphi by repeating the 'd' again");
	});
	element.focus();
});

asyncTest("handle page up and page down before the menu has focus", function() {
	expect( 1 );
	var element = $( "#menu1" ).menu({
		focus: function( event, ui ) {
			log( $( event.target ).find( ".ui-state-focus" ).parent().index() );
		}
	});

	log( "keydown", true );
	element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_DOWN } );
	element.blur();
	setTimeout( function() {
		element.simulate( "keydown", { keyCode: $.ui.keyCode.PAGE_UP } );
		equal( $("#log").html(), "0,0,keydown,", "Page Up and Page Down bring initial focus to first item");
		start();
	}, 500);
});

})(jQuery);