/*
 * Brackets Default Theme
 *
 * Defines all the variables that one can configure in a theme. This should
 * contain all variables / mixins for UI styling that we want to be able to
 * change in a theme.
 *
 * Throughout the rest of the LESS files we should _only_ use color
 * variable names that are on the LHS of the list below. So, if we
 * need a new color for some UI element, we should add a variable
 * in this file.
 */

/* Overall Colors */

/*
 * Background colors are ordered from least "intense" to most "intense"
 * So, if the background is light, then @background-color-3 should be
 * lightest, -2 should be darker, and -1 should be darker still.
 *
 * The opposite is true for a dark background -- background-color-3 should be
 * the darkest, -2 should be lighter, and -1 should be lighter still.
 */
@background-color-1: darken(@bc-white, @bc-color-step-size*2);
@background-color-2: darken(@bc-white, @bc-color-step-size);
@background-color-3: @bc-white;

/*
 * @content-color-stronger should be should be further away from the
 * background color than @content-color (i.e. more contrasty).
 *
 * @content-color-weaker should be closer to the background color
 * than @content-color (i.e. less contrasty).
 */
@content-color: lighten(@bc-black, @bc-color-step-size*2);
@content-color-stronger: @bc-black;
@content-color-weaker: lighten(@bc-black, @bc-color-step-size*6);

/* Code Styling */

/* code accent colors */
@accent-keyword: @bc-blue;
@accent-atom: @bc-violet;
@accent-number: @bc-violet;
@accent-def: @bc-yellow;
@accent-variable: @content-color;
@accent-variable-2: @content-color;
@accent-variable-3: @content-color;
@accent-property: @content-color;
@accent-operator: @content-color;
@accent-comment: @content-color-weaker;
@accent-string: @bc-green;
@accent-string-2: @bc-green;
@accent-meta: @bc-violet;
@accent-error: @bc-red;
@accent-qualifier: @bc-blue;
@accent-builtin: @bc-blue;
@accent-bracket: @content-color;
@accent-tag: @bc-blue;
@accent-attribute: @bc-yellow;
@accent-header: @bc-magenta;
@accent-quote: @bc-blue;
@accent-hr: @bc-orange;
@accent-link: @bc-violet;

.code-font() {
    color: @content-color;
    font-size: 15px;
    font-family: InconsolataMedium;
}

.code-cursor() {
    // to make a block cursor, use something like this:
    // background-color: fadeout(@blue, 50%);
    // border: none !important;

    // to make an I-cursor, use something like this:
    border-left: 1px solid black !important;

}

/* Variables and Mixins for non-code UI elements that can be styled */


define(function(require, exports, module) {

var NativeFileSystem    = require("NativeFileSystem").NativeFileSystem

,   ProjectManager      = require("ProjectManager")

,   PreferencesManager  = require("PreferencesManager");

keep keep keep keep replace keep keep keep

,   CommandManager      = require("CommandManager")

,   Commands            = require("Commands");


if(activeDoc == null && _workingSet.length > 0) {

keep keep keep keep keep replace keep keep keep

/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/**
 * ProjectManager is the model for the set of currently open project. It is responsible for
 * creating and updating the project tree when projects are opened and when changes occur to
 * the file tree.
 *
 * This module dispatches 1 event:
 *    - isFirstProjectOpen -- When the ProjectManager initializes the first
 *                            project at application start-up.
 *
 * These are jQuery events, so to listen for them you do something like this:
 *    $(ProjectManager).on("eventname", handler);
 */
define(function(require, exports, module) {
    // Load dependent non-module scripts
    require("thirdparty/jstree_pre1.0_fix_1/jquery.jstree");

    // Load dependent modules
    var NativeFileSystem    = require("NativeFileSystem").NativeFileSystem
    ,   PreferencesManager  = require("PreferencesManager")
    ,   DocumentManager     = require("DocumentManager")
    ,   CommandManager      = require("CommandManager")
    ,   Commands            = require("Commands")
    ,   Strings             = require("strings")
    ;

    /**
     * Unique PreferencesManager clientID
     */
    var PREFERENCES_CLIENT_ID = "com.adobe.brackets.ProjectManager";

    /**
     * Returns the root folder of the currently loaded project, or null if no project is open (during
     * startup, or running outside of app shell).
     * @return {DirectoryEntry}
     */
    function getProjectRoot() {
        return _projectRoot;
    }
    /**
     * @private
     * @see getProjectRoot()
     */
    var _projectRoot = null;

    /**
     * Returns true if absPath lies within the project, false otherwise.
     * FIXME: Does not support paths containing ".."
     */
    function isWithinProject(absPath) {
        var rootPath = _projectRoot.fullPath;
        if (rootPath.charAt(rootPath.length - 1) != "/") {  // TODO: standardize whether DirectoryEntry.fullPath can end in "/"
            rootPath += "/";
        }
        return (absPath.indexOf(rootPath) == 0);
    }
    /**
     * If absPath lies within the project, returns a project-relative path. Else returns absPath
     * unmodified.
     * FIXME: Does not support paths containing ".."
     */
    function makeProjectRelativeIfPossible(absPath) {
        if (isWithinProject(absPath)) {
            var relPath = absPath.slice(_projectRoot.fullPath.length);
            if (relPath.charAt(0) == '/') {  // TODO: standardize whether DirectoryEntry.fullPath can end in "/"
                relPath = relPath.slice(1);
            }
            return relPath;
        }
        return absPath;
    }

    /**
     * @private
     * Reference to the tree control
     * @type {jQueryObject}
     */
    var _projectTree = null;

    /**
     * @private
     * Used to initialize jstree state
     */
    var _projectInitialLoad =
        { previous : []
        , id : 0
        , fullPathToIdMap : {}
        };

    /**
     * @private
     * Preferences callback. Saves current project path.
     */
    function _savePreferences( storage ) {
        // save the current project
        storage.projectPath = _projectRoot.fullPath;

        // save jstree state
        var openNodes = []
        ,   projectPathLength = _projectRoot.fullPath.length + 1
        ,   entry
        ,   fullPath
        ,   shortPath
        ,   depth;

        // Query open nodes by class selector
        $(".jstree-open").each( function ( index ) {
            entry = $( this ).data("entry");

            if ( entry.fullPath ) {
                fullPath = entry.fullPath;

                // Truncate project path prefix
                shortPath = fullPath.slice( projectPathLength );

                // Determine depth of the node by counting path separators.
                // Children at the root have depth of zero.
                depth = shortPath.split("/").length - 1;

                // Map tree depth to list of open nodes
                if ( openNodes[ depth ] === undefined ) {
                    openNodes[ depth ] = [];
                }

                openNodes[ depth ].push( fullPath );
            }
        });

        // Store the open nodes by their full path and persist to storage
        storage.projectTreeState = openNodes;
    }

    /**
     * Displays a browser dialog where the user can choose a folder to load.
     * (If the user cancels the dialog, nothing more happens).
     */
    function openProject() {
        // Confirm any unsaved changes first. We run the command in "prompt-only" mode, meaning it won't
        // actually close any documents even on success; we'll do that manually after the user also oks
        //the folder-browse dialog.
        CommandManager.execute(Commands.FILE_CLOSE_ALL, true)
        .done(function() {
            // Pop up a folder browse dialog
            NativeFileSystem.showOpenDialog(false, true, "Choose a folder", null, null,
                function(files) {
                    // If length == 0, user canceled the dialog; length should never be > 1
                    if (files.length > 0) {
                        // Actually close all the old files now that we know for sure we're proceeding
                        DocumentManager.closeAll();

                        // Load the new project into the folder tree
                        loadProject( files[0] );
                    }
                },
                function(error) {
                    brackets.showModalDialog(
                          brackets.DIALOG_ID_ERROR
                        , Strings.ERROR_LOADING_PROJECT
                        , Strings.format(Strings.OPEN_DIALOG_ERROR, error.code)
                    );
                }
            );
        });
        // if fail, don't open new project: user canceled (or we failed to save its unsaved changes)
    }

    /**
     * Loads the given folder as a project. Normally, you would call openProject() instead to let the
     * user choose a folder.
     *
     * @param {string} rootPath  Absolute path to the root folder of the project.
     *  If rootPath is undefined or null, the last open project will be restored.
     * @return {Deferred} A $.Deferred() object that will be resolved when the
     *  project is loaded and tree is rendered, or rejected if the project path
     *  fails to load.
     */
    function loadProject(rootPath) {
        // reset tree node id's
        _projectInitialLoad.id = 0;

        var prefs = PreferencesManager.getPreferences(PREFERENCES_CLIENT_ID)
        ,   result = new $.Deferred()
        ,   resultRenderTree
        ,   isFirstProjectOpen = false;

        if (rootPath === null || rootPath === undefined) {
            // Load the last known project into the tree
            rootPath = prefs.projectPath;
            isFirstProjectOpen = true;

            // TODO (jasonsj): handle missing paths, see issue #100
            _projectInitialLoad.previous = prefs.projectTreeState;

            if (brackets.inBrowser) {
                // In browser: dummy folder tree (hardcoded in ProjectManager)
               rootPath = "DummyProject";
            }
        }

        // Set title
        var projectName = rootPath.substring(rootPath.lastIndexOf("/") + 1);
        $("#project-title").html(projectName);

        // Populate file tree
        if (brackets.inBrowser) {
            // Hardcoded dummy data for local testing, in jsTree JSON format
            // (we leave _projectRoot null)
            var subfolderInner = { data:"Folder_inner", children:[
                { data: "subsubfile_1" }, { data: "subsubfile_2" }
            ] };
            var treeJSONData = [
                { data: "Dummy tree content:" },
                { data:"Folder_1", children:[
                    { data: "subfile_1" }, { data: "subfile_2" }, { data: "subfile_3" }
                ] },
                { data:"Folder_2", children:[
                    { data: "subfile_4" }, subfolderInner, { data: "subfile_5" }
                ] },
                { data: "file_1" },
                { data: "file_2" }
            ];

            // Show file list in UI
            resultRenderTree = _renderTree(treeJSONData, result);

        } else {
            // Point at a real folder structure on local disk
            NativeFileSystem.requestNativeFileSystem(rootPath,
                function(rootEntry) {
                    // Success!
                    _projectRoot = rootEntry;

                    // The tree will invoke our "data provider" function to populate the top-level items, then
                    // go idle until a node is expanded - at which time it'll call us again to fetch the node's
                    // immediate children, and so on.
                    resultRenderTree = _renderTree(_treeDataProvider);
                },
                function(error) {
                    brackets.showModalDialog(
                          brackets.DIALOG_ID_ERROR
                        , Strings.ERROR_LOADING_PROJECT
                        , Strings.format(Strings.REQUEST_NATIVE_FILE_SYSTEM_ERROR, rootPath, error.code
                        , function() { result.reject(); })
                    );
                }
            );
        }

        resultRenderTree.done(function () {
            result.resolve();

            if (isFirstProjectOpen) {
                $(exports).triggerHandler("isFirstProjectOpen", _projectRoot);
            }
        });
        resultRenderTree.fail(function () {
            result.reject();
        });

        return result;
    }

    /**
     * Returns the FileEntry or DirectoryEntry corresponding to the selected item, or null
     * if no item is selected.
     *
     * @return {?Entry}
     */
    function getSelectedItem() {
        var selected = _projectTree.jstree("get_selected");
        if (selected)
            return selected.data("entry");
        return null;
    }

    /**
     * Create a new item in the project tree.
     *
     * @param baseDir {string} Full path of the directory where the item should go
     * @param initialName {string} Initial name for the item
     * @param skipRename {boolean} If true, don't allow the user to rename the item
     * @return {Deferred} A $.Deferred() object that will be resolved with the FileEntry
     *  of the created object, or rejected if the user cancelled or entered an illegal
     *  filename.
     */
    function createNewItem(baseDir, initialName, skipRename) {
        // TODO: Need API to get tree node for baseDir.
        // In the meantime, pass null for node so new item is placed
        // relative to the selection
        var node = null,
            selection = _projectTree.jstree("get_selected"),
            selectionEntry = null,
            position = "inside",
            escapeKeyPressed = false,
            result = new $.Deferred();

        // get the FileEntry or DirectoryEntry
        if ( selection ) {
            selectionEntry = selection.data("entry")
        }

        // move selection to parent DirectoryEntry
        if ( selectionEntry && selectionEntry.isFile ) {
            position = "after";

            // FIXME (jasonsj): get_parent returns the tree instead of the directory?
            /*
            selection = _projectTree.jstree("get_parent", selection);

            if ( typeof( selection.data ) == "function" ) {
                // get Entry from tree node
                // note that the jstree root will return undefined
                selectionEntry = selection.data("entry");
            }
            else {
                // reset here. will be replaced with project root.
                selectionEntry = null;
            }
            */
            // FIXME (jasonsj): hackish way to get parent directory; replace with Entry.getParent() when available
            var filePath = selectionEntry.fullPath;
            selectionEntry = new NativeFileSystem.DirectoryEntry(filePath.substring(0, filePath.lastIndexOf("/")));
        }

        // use the project root DirectoryEntry
        if ( !selectionEntry ) {
            selectionEntry = getProjectRoot();
        }

        _projectTree.on("create.jstree", function(event, data) {
            $(event.target).off("create.jstree");

            function errorCleanup() {
                // TODO: If an error occurred, we should allow the user to fix the filename.
                // For now we just remove the node so you have to start again.
                _projectTree.jstree("remove", data.rslt.obj);
                result.reject();
            }

            if (!escapeKeyPressed) {
                // Validate file name
                // TODO: There are some filenames like COM1, LPT3, etc. that are not valid on Windows.
                // We may want to add checks for those here.
                // See http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
                if (data.rslt.name.search(/[/?*:;{}<>\\|]+/) !== -1) {
                    brackets.showModalDialog(
                            brackets.DIALOG_ID_ERROR
                        ,   Strings.INVALID_FILENAME_TITLE
                        ,   Strings.INVALID_FILENAME_MESSAGE);

                    errorCleanup();
                    return;
                }

                // Use getFile() to create the new file
                selectionEntry.getFile(data.rslt.name
                    , {create: true, exclusive: true}
                    , function( entry ) {
                        data.rslt.obj.data("entry", entry);
                        _projectTree.jstree("select_node", data.rslt.obj, true);
                        result.resolve(entry);
                    }
                    , function ( error ) {
                        if ( ( error.code === FileError.PATH_EXISTS_ERR )
                             || (error.code === FileError.TYPE_MISMATCH_ERR ) ) {
                            brackets.showModalDialog(
                                  brackets.DIALOG_ID_ERROR
                                , Strings.INVALID_FILENAME_TITLE
                                , Strings.format(
                                      Strings.FILE_ALREADY_EXISTS
                                    , data.rslt.name
                                )
                            );
                        }
                        else {
                            var errString = error.code == FileError.NO_MODIFICATION_ALLOWED_ERR ?
                                             Strings.NO_MODIFICATION_ALLOWED_ERR :
                                             Strings.format(String.GENERIC_ERROR, error.code)
                            var errMsg = Strings.format(Strings.ERROR_CREATING_FILE, data.rslt.name, errString);

                            brackets.showModalDialog(
                                  brackets.DIALOG_ID_ERROR
                                , Strings.ERROR_CREATING_FILE_TITLE
                                , errMsg
                            );
                        }

                        errorCleanup();
                    }
                );
            }
            else { //escapeKeyPressed
                errorCleanup();
            }
        });

        // Create the node and open the editor
        _projectTree.jstree("create", node, position, {data: initialName}, null, skipRename);

        var renameInput = _projectTree.find(".jstree-rename-input");

        renameInput.on("keydown", function(event) {
            // Listen for escape key on keydown, so we can remove the node in the create.jstree handler above
            if (event.keyCode == 27) {
                escapeKeyPressed = true;
            }
        });

        // TODO: Figure out better way to style this input. All styles are inlined by jsTree...
        renameInput.css(
            { left: "17px"
            , height: "24px"
            }
        ).parent().css(
            { height: "26px"
            }
        );

        return result;
    }

    /**
     * @private
     * Called by jsTree when the user has expanded a node that has never been expanded before. We call
     * jsTree back asynchronously with the node's immediate children data once the subfolder is done
     * being fetched.
     *
     * @param {jQueryObject} treeNode  jQ object for the DOM node being expanded
     * @param {function(Array)} jsTreeCallback  jsTree callback to provide children to
     */
    function _treeDataProvider(treeNode, jsTreeCallback) {
        var dirEntry;

        if (treeNode == -1) {
            // Special case: root of tree
            dirEntry = _projectRoot;
        } else {
            // All other nodes: the DirectoryEntry is saved as jQ data in the tree (by _convertEntriesToJSON())
            dirEntry = treeNode.data("entry");
        }

        // Fetch dirEntry's contents
        dirEntry.createReader().readEntries(
            function(entries) {
                var subtreeJSON = _convertEntriesToJSON(entries);
                //If the list is empty, add an empty object so the loading message goes away
                if( subtreeJSON.length === 0 )
                    subtreeJSON.push({});
                jsTreeCallback(subtreeJSON);
            },
            function(error) {
                brackets.showModalDialog(
                      brackets.DIALOG_ID_ERROR
                    , Strings.ERROR_LOADING_PROJECT
                    , Strings.format(Strings.READ_DIRECTORY_ENTRIES_ERROR, dirEntry.fullPath, error.code)
                );
            }
        );

    }

    /**
     * @private
     * Given an array of NativeFileSystem entries, returns a JSON array representing them in the format
     * required by jsTree. Saves the corresponding Entry object as metadata (which jsTree will store in
     * the DOM via $.data()).
     *
     * Does NOT recursively traverse the file system: folders are marked as expandable but are given no
     * children initially.
     *
     * @param {Array.<Entry>} entries  Array of NativeFileSystem entry objects.
     * @return {Array} jsTree node data: array of JSON objects
     */
    function _convertEntriesToJSON(entries) {
        var jsonEntryList = []
        ,   entry;

        for (var entryI in entries) {
            entry = entries[entryI];

            var jsonEntry = {
                data: entry.name,
                attr: { id: "node" + _projectInitialLoad.id++ },
                metadata: { entry: entry }
            };
            if (entry.isDirectory) {
                jsonEntry.children = [];
                jsonEntry.state = "closed";
            }

            // For more info on jsTree's JSON format see: http://www.jstree.com/documentation/json_data
            jsonEntryList.push(jsonEntry);

            // Map path to ID to initialize loaded and opened states
            _projectInitialLoad.fullPathToIdMap[entry.fullPath] = jsonEntry.attr.id;
        }
        return jsonEntryList;
    }

    /**
     * @private
     * Given an input to jsTree's json_data.data setting, display the data in the file tree UI
     * (replacing any existing file tree that was previously displayed). This input could be
     * raw JSON data, or it could be a dataprovider function. See jsTree docs for details:
     * http://www.jstree.com/documentation/json_data
     */
    function _renderTree(treeDataProvider) {
        var projectTreeContainer = $("#project-files-container")
        ,   result = new $.Deferred();

        // Instantiate tree widget
        // (jsTree is smart enough to replace the old tree if there's already one there)
        _projectTree = projectTreeContainer.jstree({
            plugins : ["ui", "themes", "json_data", "crrm"],
            json_data : { data:treeDataProvider, correct_state: false },
            core : { animation:0 },
            themes : { theme:"brackets", url:"styles/jsTreeTheme.css", dots:false, icons:false },
                //(note: our actual jsTree theme CSS lives in brackets.less; we specify an empty .css
                // file because jsTree insists on loading one itself)

            strings : { loading : "Loading ...", new_node : "New node" }    // TODO: localization
        })
        .bind("select_node.jstree", function(event, data) {
            var entry = data.rslt.obj.data("entry");
            if (entry.isFile)
                CommandManager.execute(Commands.FILE_OPEN, entry.fullPath);
        })
        .bind("reopen.jstree", function(event, data) {
            // This handler fires for the initial load and subsequent
            // reload_nodes events. For each depth level of the tree, we open
            // the saved nodes by a fullPath lookup.
            if ( _projectInitialLoad.previous.length > 0 ) {
                // load previously open nodes by increasing depth
                var toOpenPaths = _projectInitialLoad.previous.shift()
                ,   toOpenIds   = [];

                // use path to lookup ID
                $.each( toOpenPaths, function(index, value) {
                    toOpenIds.push(_projectInitialLoad.fullPathToIdMap[value]);
                });

                // specify nodes to open and load
                data.inst.data.core.to_open = toOpenIds;
                _projectTree.jstree("reload_nodes", false);
            }
            if ( _projectInitialLoad.previous.length === 0 ) {
                result.resolve();
            }
        })
        .bind("dblclick.jstree", function(event) {
            var entry = $(event.target).closest("li").data("entry");
            if (entry.isFile) {
                CommandManager.execute(Commands.FILE_ADD_TO_WORKING_SET, entry.fullPath);
            }
        });

        return result;
    };

    // Define public API
    exports.getProjectRoot  = getProjectRoot;
    exports.isWithinProject = isWithinProject;
    exports.makeProjectRelativeIfPossible = makeProjectRelativeIfPossible;
    exports.openProject     = openProject;
    exports.loadProject     = loadProject;
    exports.getSelectedItem = getSelectedItem;
    exports.createNewItem   = createNewItem;

    // Initialize now
    (function() {
        var loadedPath = window.location.pathname;
        var bracketsSrc = loadedPath.substr(0, loadedPath.lastIndexOf("/"));
        var defaults =
            { projectPath:      bracketsSrc /* initialze to brackets source */
            , projectTreeState: ""          /* TODO (jasonsj): jstree state */
            };
        PreferencesManager.addPreferencesClient(PREFERENCES_CLIENT_ID, _savePreferences, this, defaults);
    })();
});


body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }

#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
#HTMLReporter a { text-decoration: none; }
#HTMLReporter a:hover { text-decoration: underline; }
#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
#HTMLReporter .version { color: #aaaaaa; }
#HTMLReporter .banner { margin-top: 14px; }
#HTMLReporter .duration { color: #aaaaaa; float: right; }
#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
#HTMLReporter .runningAlert { background-color: #666666; }
#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
#HTMLReporter .passingAlert { background-color: #a6b779; }
#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
#HTMLReporter .failingAlert { background-color: #cf867e; }
#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
#HTMLReporter .results { margin-top: 14px; }
#HTMLReporter #details { display: none; }
#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter.showDetails .summary { display: none; }
#HTMLReporter.showDetails #details { display: block; }
#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
#HTMLReporter .summary { margin-top: 14px; }
#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
#HTMLReporter .description + .suite { margin-top: 0; }
#HTMLReporter .suite { margin-top: 14px; }
#HTMLReporter .suite a { color: #333333; }
#HTMLReporter #details .specDetail { margin-bottom: 28px; }
#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
#HTMLReporter .resultMessage span.result { display: block; }
#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }

#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
#TrivialReporter .runner.running { background-color: yellow; }
#TrivialReporter .options { text-align: right; font-size: .8em; }
#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
#TrivialReporter .suite .suite { margin: 5px; }
#TrivialReporter .suite.passed { background-color: #dfd; }
#TrivialReporter .suite.failed { background-color: #fdd; }
#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
#TrivialReporter .spec.skipped { background-color: #bbb; }
#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
#TrivialReporter .passed { background-color: #cfc; display: none; }
#TrivialReporter .failed { background-color: #fbb; }
#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
#TrivialReporter .resultMessage .mismatch { color: black; }
#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }


/* Patterns.less
 * Repeatable UI elements outside the base styles provided from the scaffolding
 * ---------------------------------------------------------------------------- */

// TOPBAR
// ------

// Topbar for Branding and Nav
.topbar {
  height: 40px;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 10000;
  overflow: visible;

  // Links get text shadow
  a {
    color: @grayLight;
    text-shadow: 0 -1px 0 rgba(0,0,0,.25);
  }

  // Hover and active states
  // h3 for backwards compatibility
  h3 a:hover,
  .brand:hover,
  ul .active > a {
    background-color: #333;
    background-color: rgba(255,255,255,.05);
    color: @white;
    text-decoration: none;
  }

  // Website name
  // h3 left for backwards compatibility
  h3 {
    position: relative;
  }
  h3 a,
  .brand {
    float: left;
    display: block;
    padding: 8px 20px 12px;
    margin-left: -20px; // negative indent to left-align the text down the page
    color: @white;
    font-size: 20px;
    font-weight: 200;
    line-height: 1;
  }

  // Plain text in topbar
  p {
    margin: 0;
    line-height: 40px;
    a:hover {
      background-color: transparent;
      color: @white;
    }
  }

  // Search Form
  form {
    float: left;
    margin: 5px 0 0 0;
    position: relative;
    .opacity(100);
  }
  // Todo: remove from v2.0 when ready, added for legacy
  form.pull-right {
    float: right;
  }
  input {
    background-color: #444;
    background-color: rgba(255,255,255,.3);
    #font > .sans-serif(13px, normal, 1);
    padding: 4px 9px;
    color: @white;
    color: rgba(255,255,255,.75);
    border: 1px solid #111;
    .border-radius(4px);
    @shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0px rgba(255,255,255,.25);
    .box-shadow(@shadow);
    .transition(none);

    // Placeholder text gets special styles; can't be bundled together though for some reason
    &:-moz-placeholder {
      color: @grayLighter;
    }
    &::-webkit-input-placeholder {
      color: @grayLighter;
    }
    // Hover states
    &:hover {
      background-color: @grayLight;
      background-color: rgba(255,255,255,.5);
      color: @white;
    }
    // Focus states (we use .focused since IE8 and down doesn't support :focus)
    &:focus,
    &.focused {
      outline: 0;
      background-color: @white;
      color: @grayDark;
      text-shadow: 0 1px 0 @white;
      border: 0;
      padding: 5px 10px;
      .box-shadow(0 0 3px rgba(0,0,0,.15));
    }
  }
}

// gradient is applied to it's own element because overflow visible is not honored by ie when filter is present
// For backwards compatibility, include .topbar .fill
.topbar-inner,
.topbar .fill {
  background-color: #222;
  #gradient > .vertical(#333, #222);
  @shadow: 0 1px 3px rgba(0,0,0,.25), inset 0 -1px 0 rgba(0,0,0,.1);
  .box-shadow(@shadow);
}

// NAVIGATION
// ----------

// Topbar Nav
// ul.nav for all topbar based navigation to avoid inheritance issues and over-specificity
// For backwards compatibility, leave in .topbar div > ul
.topbar div > ul,
.nav {
  display: block;
  float: left;
  margin: 0 10px 0 0;
  position: relative;
  left: 0;
  > li {
    display: block;
    float: left;
  }
  a {
    display: block;
    float: none;
    padding: 10px 10px 11px;
    line-height: 19px;
    text-decoration: none;
    &:hover {
      color: @white;
      text-decoration: none;
    }
  }
  .active > a {
    background-color: #222;
    background-color: rgba(0,0,0,.5);
  }

  // Secondary (floated right) nav in topbar
  &.secondary-nav {
    float: right;
    margin-left: 10px;
    margin-right: 0;
    // backwards compatibility
    .menu-dropdown,
    .dropdown-menu {
      right: 0;
      border: 0;
    }
  }
  // Dropdowns within the .nav
  // a.menu:hover and li.open .menu for backwards compatibility
  a.menu:hover,
  li.open .menu,
  .dropdown-toggle:hover,
  .dropdown.open .dropdown-toggle {
    background: #444;
    background: rgba(255,255,255,.05);
  }
  // .menu-dropdown for backwards compatibility
  .menu-dropdown,
  .dropdown-menu {
    background-color: #333;
    // a.menu for backwards compatibility
    a.menu,
    .dropdown-toggle {
      color: @white;
      &.open {
        background: #444;
        background: rgba(255,255,255,.05);
      }
    }
    li a {
      color: #999;
      text-shadow: 0 1px 0 rgba(0,0,0,.5);
      &:hover {
        #gradient > .vertical(#292929,#191919);
        color: @white;
      }
    }
    .active a {
      color: @white;
    }
    .divider {
      background-color: #222;
      border-color: #444;
    }
  }
}

// For backwards compatibility with new dropdowns, redeclare dropdown link padding
.topbar ul .menu-dropdown li a,
.topbar ul .dropdown-menu li a {
  padding: 4px 15px;
}

// Dropdown Menus
// Use the .menu class on any <li> element within the topbar or ul.tabs and you'll get some superfancy dropdowns
// li.menu for backwards compatibility
li.menu,
.dropdown {
  position: relative;
}
// The link that is clicked to toggle the dropdown
// a.menu for backwards compatibility
a.menu:after,
.dropdown-toggle:after {
  width: 0;
  height: 0;
  display: inline-block;
  content: "↓";
  text-indent: -99999px;
  vertical-align: top;
  margin-top: 8px;
  margin-left: 4px;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4px solid @white;
  .opacity(50);
}
// The dropdown menu (ul)
// .menu-dropdown for backwards compatibility
.menu-dropdown,
.dropdown-menu {
  background-color: @white;
  float: left;
  display: none; // None by default, but block on "open" of the menu
  position: absolute;
  top: 40px;
  z-index: 900;
  min-width: 160px;
  max-width: 220px;
  _width: 160px;
  margin-left: 0; // override default ul styles
  margin-right: 0;
  padding: 6px 0;
  zoom: 1; // do we need this?
  border-color: #999;
  border-color: rgba(0,0,0,.2);
  border-style: solid;
  border-width: 0 1px 1px;
  .border-radius(0 0 6px 6px);
  .box-shadow(0 2px 4px rgba(0,0,0,.2));
  .background-clip(padding-box);

  // Unfloat any li's to make them stack
  li {
    float: none;
    display: block;
    background-color: none;
  }
  // Dividers (basically an hr) within the dropdown
  .divider {
    height: 1px;
    margin: 5px 0;
    overflow: hidden;
    background-color: #eee;
    border-bottom: 1px solid @white;
  }
}

.topbar .dropdown-menu,
.dropdown-menu {
  // Links within the dropdown menu
  a {
    display: block;
    padding: 4px 15px;
    clear: both;
    font-weight: normal;
    line-height: 18px;
    color: @gray;
    text-shadow: 0 1px 0 @white;
    // Hover state
    &:hover,
    &.hover {
      #gradient > .vertical(#eeeeee, #dddddd);
      color: @grayDark;
      text-decoration: none;
      @shadow: inset 0 1px 0 rgba(0,0,0,.025), inset 0 -1px rgba(0,0,0,.025);
      .box-shadow(@shadow);
    }
  }
}

// Open state for the dropdown
// .open for backwards compatibility
.open,
.dropdown.open {
  // .menu for backwards compatibility
  .menu,
  .dropdown-toggle {
    color: @white;
    background: #ccc;
    background: rgba(0,0,0,.3);
  }
  // .menu-dropdown for backwards compatibility
  .menu-dropdown,
  .dropdown-menu {
    display: block;
  }
}

// TABS AND PILLS
// --------------

// Common styles
.tabs,
.pills {
  margin: 0 0 @baseline;
  padding: 0;
  list-style: none;
  .clearfix();
  > li {
    float: left;
    > a {
      display: block;
    }
  }
}

// Tabs
.tabs {
  border-color: #ddd;
  border-style: solid;
  border-width: 0 0 1px;
  > li {
    position: relative; // For the dropdowns mostly
    margin-bottom: -1px;
    > a {
      padding: 0 15px;
      margin-right: 2px;
      line-height: (@baseline * 2) - 2;
      border: 1px solid transparent;
      .border-radius(4px 4px 0 0);
      &:hover {
        text-decoration: none;
        background-color: #eee;
        border-color: #eee #eee #ddd;
      }
    }
  }
  // Active state, and it's :hover to override normal :hover
  .active > a,
  .active > a:hover {
    color: @gray;
    background-color: @white;
    border: 1px solid #ddd;
    border-bottom-color: transparent;
    cursor: default;
  }
}

// Dropdowns in tabs
.tabs {
  // first one for backwards compatibility
  .menu-dropdown,
  .dropdown-menu {
    top: 35px;
    border-width: 1px;
    .border-radius(0 6px 6px 6px);
  }
  // first one for backwards compatibility
  a.menu:after,
  .dropdown-toggle:after {
    border-top-color: #999;
    margin-top: 15px;
    margin-left: 5px;
  }
  // first one for backwards compatibility
  li.open.menu .menu,
  .open.dropdown .dropdown-toggle {
    border-color: #999;
  }
  // first one for backwards compatibility
  li.open a.menu:after,
  .dropdown.open .dropdown-toggle:after {
    border-top-color: #555;
  }
}

// Pills
.pills {
  a {
    margin: 5px 3px 5px 0;
    padding: 0 15px;
    line-height: 30px;
    text-shadow: 0 1px 1px @white;
    .border-radius(15px);
    &:hover {
      color: @white;
      text-decoration: none;
      text-shadow: 0 1px 1px rgba(0,0,0,.25);
      background-color: @linkColorHover;
    }
  }
  .active a {
    color: @white;
    text-shadow: 0 1px 1px rgba(0,0,0,.25);
    background-color: @linkColor;
  }
}

// Stacked pills
.pills-vertical > li {
  float: none;
}

// Tabbable areas
.tab-content,
.pill-content {
}
.tab-content > .tab-pane,
.pill-content > .pill-pane {
  display: none;
}
.tab-content > .active,
.pill-content > .active {
  display: block;
}

// BREADCRUMBS
// -----------

.breadcrumb {
  padding: 7px 14px;
  margin: 0 0 @baseline;
  #gradient > .vertical(#ffffff, #f5f5f5);
  border: 1px solid #ddd;
  .border-radius(3px);
  .box-shadow(inset 0 1px 0 @white);
  li {
    display: inline;
    text-shadow: 0 1px 0 @white;
  }
  .divider {
    padding: 0 5px;
    color: @grayLight;
  }
  .active a {
    color: @grayDark;
  }
}

// PAGE HEADERS
// ------------

.hero-unit {
  background-color: #f5f5f5;
  margin-bottom: 30px;
  padding: 60px;
  .border-radius(6px);
  h1 {
    margin-bottom: 0;
    font-size: 60px;
    line-height: 1;
    letter-spacing: -1px;
  }
  p {
    font-size: 18px;
    font-weight: 200;
    line-height: @baseline * 1.5;
  }
}
footer {
  margin-top: @baseline - 1;
  padding-top: @baseline - 1;
  border-top: 1px solid #eee;
}

// PAGE HEADERS
// ------------

.page-header {
  margin-bottom: @baseline - 1;
  border-bottom: 1px solid #ddd;
  .box-shadow(0 1px 0 rgba(255,255,255,.5));
  h1 {
    margin-bottom: (@baseline / 2) - 1px;
  }
}

// BUTTON STYLES
// -------------

// Shared colors for buttons and alerts
.btn,
.alert-message {
  // Set text color
  &.danger,
  &.danger:hover,
  &.error,
  &.error:hover,
  &.success,
  &.success:hover,
  &.info,
  &.info:hover {
    color: @white
  }
  // Sets the close button to the middle of message
  .close{
    font-family: Arial, sans-serif;
    line-height: 18px;
  }
  // Danger and error appear as red
  &.danger,
  &.error {
    .gradientBar(#ee5f5b, #c43c35);
  }
  // Success appears as green
  &.success {
    .gradientBar(#62c462, #57a957);
  }
  // Info appears as a neutral blue
  &.info {
    .gradientBar(#5bc0de, #339bb9);
  }
}

// Base .btn styles
.btn {
  // Button Base
  cursor: pointer;
  display: inline-block;
  #gradient > .vertical-three-colors(#ffffff, #ffffff, 25%, darken(#ffffff, 10%)); // Don't use .gradientbar() here since it does a three-color gradient
  padding: 5px 14px 6px;
  text-shadow: 0 1px 1px rgba(255,255,255,.75);
  color: #333;
  font-size: @basefont;
  line-height: normal;
  border: 1px solid #ccc;
  border-bottom-color: #bbb;
  .border-radius(4px);
  @shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
  .box-shadow(@shadow);

  &:hover {
    background-position: 0 -15px;
    color: #333;
    text-decoration: none;
  }

  // Focus state for keyboard and accessibility
  &:focus {
    outline: 1px dotted #666;
  }

  // Primary Button Type
  &.primary {
    color: @white;
    .gradientBar(@blue, @blueDark)
  }

   // Transitions
  .transition(.1s linear all);

  // Active and Disabled states
  &.active,
  :active {
    @shadow: inset 0 2px 4px rgba(0,0,0,.25), 0 1px 2px rgba(0,0,0,.05);
    .box-shadow(@shadow);
  }
  &.disabled {
    cursor: default;
    background-image: none;
    .reset-filter();
    .opacity(65);
    .box-shadow(none);
  }
  &[disabled] {
    // disabled pseudo can't be included with .disabled
    // def because IE8 and below will drop it ;_;
    cursor: default;
    background-image: none;
    .reset-filter();
    .opacity(65);
    .box-shadow(none);
  }

  // Button Sizes
  &.large {
    font-size: @basefont + 2px;
    line-height: normal;
    padding: 9px 14px 9px;
    .border-radius(6px);
  }
  &.small {
    padding: 7px 9px 7px;
    font-size: @basefont - 2px;
  }
}
// Super jank hack for removing border-radius from IE9 so we can keep filter gradients on alerts and buttons
:root .alert-message,
:root .btn {
  border-radius: 0 \0;
}

// Help Firefox not be a jerk about adding extra padding to buttons
button.btn,
input[type=submit].btn {
  &::-moz-focus-inner {
      padding: 0;
      border: 0;
  }
}

// CLOSE ICONS
// -----------
.close {
  float: right;
  color: @black;
  font-size: 20px;
  font-weight: bold;
  line-height: @baseline * .75;
  text-shadow: 0 1px 0 rgba(255,255,255,1);
  .opacity(25);
  &:hover {
    color: @black;
    text-decoration: none;
    .opacity(40);
  }
}

// ERROR STYLES
// ------------

// Base alert styles
.alert-message {
  position: relative;
  padding: 7px 15px;
  margin-bottom: @baseline;
  color: @grayDark;
  .gradientBar(#fceec1, #eedc94); // warning by default
  text-shadow: 0 1px 0 rgba(255,255,255,.5);
  border-width: 1px;
  border-style: solid;
  .border-radius(4px);
  .box-shadow(inset 0 1px 0 rgba(255,255,255,.25));

  // Adjust close icon
  .close {
    margin-top: 1px;
    *margin-top: 0; // For IE7
  }

  // Make links same color as text and stand out more
  a {
    font-weight: bold;
    color: @grayDark;
  }
  &.danger p a,
  &.error p a,
  &.success p a,
  &.info p a {
    color: @white;
  }

  // Remove extra margin from content
  h5 {
    line-height: @baseline;
  }
  p {
    margin-bottom: 0;
  }
  div {
    margin-top: 5px;
    margin-bottom: 2px;
    line-height: 28px;
  }
  .btn {
    // Provide actions with buttons
    .box-shadow(0 1px 0 rgba(255,255,255,.25));
  }

  &.block-message {
    background-image: none;
    background-color: lighten(#fceec1, 5%);
    .reset-filter();
    padding: 14px;
    border-color: #fceec1;
    .box-shadow(none);
    ul, p {
      margin-right: 30px;
    }
    ul {
      margin-bottom: 0;
    }
    li {
      color: @grayDark;
    }
    .alert-actions {
      margin-top: 5px;
    }
    &.error,
    &.success,
    &.info {
      color: @grayDark;
      text-shadow: 0 1px 0 rgba(255,255,255,.5);
    }
    &.error {
      background-color: lighten(#f56a66, 25%);
      border-color: lighten(#f56a66, 20%);
    }
    &.success {
      background-color: lighten(#62c462, 30%);
      border-color: lighten(#62c462, 25%);
    }
    &.info {
      background-color: lighten(#6bd0ee, 25%);
      border-color: lighten(#6bd0ee, 20%);
    }
    // Change link color back
    &.danger p a,
    &.error p a,
    &.success p a,
    &.info p a {
      color: @grayDark;
    }

  }
}

// PAGINATION
// ----------

.pagination {
  height: @baseline * 2;
  margin: @baseline 0;
  ul {
    float: left;
    margin: 0;
    border: 1px solid #ddd;
    border: 1px solid rgba(0,0,0,.15);
    .border-radius(3px);
    .box-shadow(0 1px 2px rgba(0,0,0,.05));
  }
  li {
    display: inline;
  }
  a {
    float: left;
    padding: 0 14px;
    line-height: (@baseline * 2) - 2;
    border-right: 1px solid;
    border-right-color: #ddd;
    border-right-color: rgba(0,0,0,.15);
    *border-right-color: #ddd; /* IE6-7 */
    text-decoration: none;
  }
  a:hover,
  .active a {
    background-color: lighten(@blue, 45%);
  }
  .disabled a,
  .disabled a:hover {
    background-color: transparent;
    color: @grayLight;
  }
  .next a {
    border: 0;
  }
}

// WELLS
// -----

.well {
  background-color: #f5f5f5;
  margin-bottom: 20px;
  padding: 19px;
  min-height: 20px;
  border: 1px solid #eee;
  border: 1px solid rgba(0,0,0,.05);
  .border-radius(4px);
  .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
  blockquote {
    border-color: #ddd;
    border-color: rgba(0,0,0,.15);
  }
}

// MODALS
// ------

.modal-backdrop {
  background-color: @black;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10000;
  // Fade for backdrop
  &.fade { opacity: 0; }
}

.modal-backdrop,
.modal-backdrop.fade.in {
  .opacity(80);
}

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 11000;
  width: 560px;
  margin: -250px 0 0 -280px;
  background-color: @white;
  border: 1px solid #999;
  border: 1px solid rgba(0,0,0,.3);
  *border: 1px solid #999; /* IE6-7 */
  .border-radius(6px);
  .box-shadow(0 3px 7px rgba(0,0,0,0.3));
  .background-clip(padding-box);
  .close { margin-top: 7px; }
  &.fade {
    .transition(e('opacity .3s linear, top .3s ease-out'));
    top: -25%;
  }
  &.fade.in { top: 50%; }
}
.modal-header {
  border-bottom: 1px solid #eee;
  padding: 5px 15px;
}
.modal-body {
  padding: 15px;
}
.modal-body form {
  margin-bottom: 0;
}
.modal-footer {
  background-color: #f5f5f5;
  padding: 14px 15px 15px;
  border-top: 1px solid #ddd;
  .border-radius(0 0 6px 6px);
  .box-shadow(inset 0 1px 0 @white);
  .clearfix();
  margin-bottom: 0;
  .btn {
    float: right;
    margin-left: 5px;
  }
}

// Fix the stacking of these components when in modals
.modal .popover,
.modal .twipsy {
  z-index: 12000;
}

// POPOVER ARROWS
// --------------

#popoverArrow {
  .above(@arrowWidth: 5px) {
    bottom: 0;
    left: 50%;
    margin-left: -@arrowWidth;
    border-left: @arrowWidth solid transparent;
    border-right: @arrowWidth solid transparent;
    border-top: @arrowWidth solid @black;
  }
  .left(@arrowWidth: 5px) {
    top: 50%;
    right: 0;
    margin-top: -@arrowWidth;
    border-top: @arrowWidth solid transparent;
    border-bottom: @arrowWidth solid transparent;
    border-left: @arrowWidth solid @black;
  }
  .below(@arrowWidth: 5px) {
    top: 0;
    left: 50%;
    margin-left: -@arrowWidth;
    border-left: @arrowWidth solid transparent;
    border-right: @arrowWidth solid transparent;
    border-bottom: @arrowWidth solid @black;
  }
  .right(@arrowWidth: 5px) {
    top: 50%;
    left: 0;
    margin-top: -@arrowWidth;
    border-top: @arrowWidth solid transparent;
    border-bottom: @arrowWidth solid transparent;
    border-right: @arrowWidth solid @black;
  }
}

// TWIPSY
// ------

.twipsy {
  display: block;
  position: absolute;
  visibility: visible;
  padding: 5px;
  font-size: 11px;
  z-index: 1000;
  .opacity(80);
  &.fade.in {
    .opacity(80);
  }
  &.above .twipsy-arrow   { #popoverArrow > .above(); }
  &.left .twipsy-arrow    { #popoverArrow > .left(); }
  &.below .twipsy-arrow   { #popoverArrow > .below(); }
  &.right .twipsy-arrow   { #popoverArrow > .right(); }
}
.twipsy-inner {
  padding: 3px 8px;
  background-color: @black;
  color: white;
  text-align: center;
  max-width: 200px;
  text-decoration: none;
  .border-radius(4px);
}
.twipsy-arrow {
  position: absolute;
  width: 0;
  height: 0;
}

// POPOVERS
// --------

.popover {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1000;
  padding: 5px;
  display: none;
  &.above .arrow { #popoverArrow > .above(); }
  &.right .arrow { #popoverArrow > .right(); }
  &.below .arrow { #popoverArrow > .below(); }
  &.left .arrow  { #popoverArrow > .left(); }
  .arrow {
    position: absolute;
    width: 0;
    height: 0;
  }
  .inner {
    background: @black;
    background: rgba(0,0,0,.8);
    padding: 3px;
    overflow: hidden;
    width: 280px;
    .border-radius(6px);
    .box-shadow(0 3px 7px rgba(0,0,0,0.3));
  }
  .title {
    background-color: #f5f5f5;
    padding: 9px 15px;
    line-height: 1;
    .border-radius(3px 3px 0 0);
    border-bottom:1px solid #eee;
  }
  .content {
    background-color: @white;
    padding: 14px;
    .border-radius(0 0 3px 3px);
    .background-clip(padding-box);
    p, ul, ol {
      margin-bottom: 0;
    }
  }
}

// PATTERN ANIMATIONS
// ------------------

.fade {
  .transition(opacity .15s linear);
  opacity: 0;
  &.in {
    opacity: 1;
  }
}

// LABELS
// ------

.label {
  padding: 1px 3px 2px;
  font-size: @basefont * .75;
  font-weight: bold;
  color: @white;
  text-transform: uppercase;
  white-space: nowrap;
  background-color: @grayLight;
  .border-radius(3px);
  &.important { background-color: #c43c35; }
  &.warning   { background-color: @orange; }
  &.success   { background-color: @green; }
  &.notice    { background-color: lighten(@blue, 25%); }
}

// MEDIA GRIDS
// -----------

.media-grid {
  margin-left: -@gridGutterWidth;
  margin-bottom: 0;
  .clearfix();
  li {
    display: inline;
  }
  a {
    float: left;
    padding: 4px;
    margin: 0 0 @baseline @gridGutterWidth;
    border: 1px solid #ddd;
    .border-radius(4px);
    .box-shadow(0 1px 1px rgba(0,0,0,.075));
    img {
      display: block;
    }
    &:hover {
      border-color: @linkColor;
      .box-shadow(0 1px 4px rgba(0,105,214,.25));
    }
  }
}


// Set the baseUrl to brackets/src
require.config({
    baseUrl: "../src"
});

define(function(require, exports, module) {
    // Utility dependency
    var SpecRunnerUtils = require("spec/SpecRunnerUtils.js");

    // Unique key for unit testing
    var PreferencesManager = require("PreferencesManager");
    PreferencesManager._setStorageKey( SpecRunnerUtils.TEST_PREFERENCES_KEY );

    // Load test specs
    require("spec/LowLevelFileIO-test.js");
    require("spec/FileCommandHandlers-test.js");
    require("spec/NativeFileSystem-test.js");
    require("spec/PreferencesManager-test.js");
    require("spec/Editor-test.js");
    require("spec/ProjectManager-test.js");
    require("spec/WorkingSetView-test.js");

});


$(ProjectManager).on("initializeComplete", function(event, projectRoot) {

/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/**
 * DocumentManager is the model for the set of currently 'open' files and their contents. It controls
 * which file is currently shown in the editor, the dirty bit for all files, and the list of documents
 * in the working set.
 *
 * Each Document also encapsulates the editor widget for that file, which in turn controls each file's
 * contents and undo history. (So this is not a perfectly clean, headless model, but that's unavoidable
 * because the document state is tied up in the CodeMirror UI). We share ownership of the editor objects
 * with EditorManager, which creates, shows/hides, resizes, and disposes the editors.
 *
 * 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.
 *    - currentDocumentChange -- When the value of getCurrentDocument() changes.
 *    - workingSetAdd -- When a Document is added to the working set (see getWorkingSet()). The 2nd arg
 *      to the listener is the added Document.
 *    - workingSetRemove -- When a Document is removed from the working set (see getWorkingSet()). The
 *      2nd arg to the listener is the removed Document.
 *
 * These are jQuery events, so to listen for them you do something like this:
 *    $(DocumentManager).on("eventname", handler);
 */
define(function(require, exports, module) {

    var NativeFileSystem    = require("NativeFileSystem").NativeFileSystem
    ,   ProjectManager      = require("ProjectManager")
    ,   PreferencesManager  = require("PreferencesManager");

    /**
     * Unique PreferencesManager clientID
     */
    var PREFERENCES_CLIENT_ID = "com.adobe.brackets.DocumentManager";

    /**
     * @constructor
     * A single editable document, e.g. an entry in the working set list. Documents are unique per
     * file, so it IS safe to compare them with '==' or '==='.
     * @param {!FileEntry} file  The file being edited. Need not lie within the project.
     * @param {!CodeMirror} editor  Optional. The editor that will maintain the document state (current text
     *          and undo stack). It is assumed that the editor text has already been initialized
     *          with the file's contents. The editor may be null when the working set is restored
     *          at initialization.
     */
    function Document(file, editor) {
        if (!(this instanceof Document)) {  // error if constructor called without 'new'
            throw new Error("Document constructor must be called with 'new'");
        }
        if (getDocumentForFile(file) != null) {
            throw new Error("Creating a document + editor when one already exists, for: " + file);
        }

        this.file = file;
        this._setEditor(editor);
    }

    /**
     * The FileEntry for the document being edited.
     * @type {!FileEntry}
     */
    Document.prototype.file = null;

    /**
     * Whether this document has unsaved changes or not.
     * When this changes on any Document, DocumentManager dispatches a "dirtyFlagChange" event.
     * @type {boolean}
     */
    Document.prototype.isDirty = false;

    /**
     * @private
     * NOTE: this is actually "semi-private"; EditorManager also accesses this field. But no one
     * other than DocumentManager and EditorManager should access it.
     * The editor may be null in the case that the document working set was
     * restored from storage but an editor was not yet created.
     * TODO: we should close on whether private fields are declared on the prototype like this (vs.
     * just set in the constructor).
     * @type {!CodeMirror}
     */
    Document.prototype._editor = null;

    /**
     * @private
     * Initialize the editor instance for this file.
     */
    Document.prototype._setEditor = function(editor) {
        if (editor === undefined) {
            return;
        }

        // Editor can only be assigned once per Document
        console.assert(this._editor === null);

        this._editor = editor;

        // Dirty-bit tracking
        editor.setOption("onChange", this._updateDirty.bind(this));
        this._savedUndoPosition = editor.historySize().undo;   // should always be 0, but just to be safe...
    }

    /**
     * @private
     * TODO: we should close on whether private fields are declared on the prototype like this
     * @type {number}
     */
    Document.prototype._savedUndoPosition = 0;

    /**
     * @return {string} The editor's current contents; may not be saved to disk
     *  yet. Returns null if the file was not yet read and no editor was
     *  created.
     */
    Document.prototype.getText = function() {
        console.assert(this._editor != null);

        return this._editor.getValue();
    }

    /**
     * @private
     */
    Document.prototype._updateDirty = function() {
        if (this._editor == null) {
            return;
        }

        // If we've undone past the undo position at the last save, and there is no redo stack,
        // then we can never get back to a non-dirty state.
        var historySize = this._editor.historySize();
        if (historySize.undo < this._savedUndoPosition && historySize.redo == 0) {
            this._savedUndoPosition = -1;
        }
        var newIsDirty = (this._editor.historySize().undo != this._savedUndoPosition);

        if (this.isDirty != newIsDirty) {
            this.isDirty = newIsDirty;

            // Dispatch event
            $(exports).triggerHandler("dirtyFlagChange", this);

            // If file just became dirty, add it to working set (if not already there)
            if (newIsDirty)
                addToWorkingSet(this);
        }
    }

    /** Marks the document not dirty. Should be called after the document is saved to disk. */
    Document.prototype.markClean = function() {
        if (this._editor === null) {
            return;
        }

        this._savedUndoPosition = this._editor.historySize().undo;
        this._updateDirty();
    }

    /* (pretty toString(), to aid debugging) */
    Document.prototype.toString = function() {
        return "[Document " + this.file.fullPath + " " + (this.isDirty ? "(dirty!)" : "(clean)") + "]";
    }

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

    /**
     * Returns the Document that is currently open in the editor UI. May be null.
     * When this changes, DocumentManager dispatches a "currentDocumentChange" event.
     * @return {?Document}
     */
    function getCurrentDocument() {
        return _currentDocument;
    }

    /**
     * If the given file is 'open' for editing, returns its Document. Else returns null. "Open for
     * editing" means either the file is in the working set, and/or the file is currently open in
     * the editor UI.
     * @param {!FileEntry} fileEntry
     * @return {?Document}
     */
    function getDocumentForFile(fileEntry) {
        if (_currentDocument && _currentDocument.file.fullPath == fileEntry.fullPath)
            return _currentDocument;

        var wsIndex = _findInWorkingSet(fileEntry);
        if (wsIndex != -1)
            return _workingSet[wsIndex];

        return null;
    }

    /**
     * @private
     * @see DocumentManager.getWorkingSet()
     */
    var _workingSet = [];

    /**
     * Returns an ordered list of items in the working set. May be 0-length, but never null.
     *
     * When a Document is added this list, DocumentManager dispatches a "workingSetAdd" event.
     * When a Document 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.<Document>}
     */
    function getWorkingSet() {
        return _workingSet;
        // TODO: return a clone to prevent meddling?
    }

    /**
     * Adds the given document to the end of the working set list, if it is not already in the list.
     * Does not change which document is currently open in the editor.
     * @param {!Document} document
     */
    function addToWorkingSet(document) {
        // If doc is already in working set, do nothing
        if (_findInWorkingSet(document.file) != -1)
            return;

        // Add
        _workingSet.push(document);

        // Dispatch event
        $(exports).triggerHandler("workingSetAdd", document);
    }

    /**
     * Removes the given document from the working set list, if it was in the list. Does not change
     * the editor even if this document is the one currently open;.
     * @param {!Document} document
     */
    function _removeFromWorkingSet(document) {
        // If doc isn't in working set, do nothing
        var index = _findInWorkingSet(document.file);
        if (index == -1)
            return;

        // Remove
        _workingSet.splice(index, 1);

        // Dispatch event
        $(exports).triggerHandler("workingSetRemove", document);
    }

    /**
     * @param {!FileEntry} fileEntry
     */
    function _findInWorkingSet(fileEntry) {
        for (var i = 0; i < _workingSet.length; i++) {
            if (_workingSet[i].file.fullPath == fileEntry.fullPath)
                return i;
        }
        return -1;
    }

    /**
     * Displays the given file in the editor pane. May also add the item to the working set list.
     * This changes the value of getCurrentDocument(), which will trigger listeners elsewhere in the
     * UI that in turn do things like update the selection in the file tree / working set UI.
     *
     * @param {!Document} document  The document whose editor should be shown. May or may not
     *      already be in the working set.
     */
    function showInEditor(document) {
        // If this file is already in editor, do nothing
        if (_currentDocument == document)
            return;

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

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

    /** Closes the currently visible document, if any */
    function _clearEditor() {
        // If editor already blank, do nothing
        if (_currentDocument == null)
            return;

        // Change model & dispatch event
        _currentDocument = null;
        $(exports).triggerHandler("currentDocumentChange");
        // (this event triggers EditorManager to actually clear the editor UI)
    }

    /**
     * Asynchronously reads a file as UTF-8 encoded text.
     * @return {Deferred} a jQuery Deferred that will be resolved with the
     *  file text content for the fileEntry, or rejected with a FileError if
     *  the file can not be read.
     */
    function readAsText(fileEntry) {
        var result = new $.Deferred()
        ,   reader = new NativeFileSystem.FileReader();

        fileEntry.file(function(file) {
            reader.onload = function(event) {
                result.resolve(event.target.result);
            };

            reader.onerror = function(event) {
                result.reject(event.target.error);
            };

            reader.readAsText(file, "utf8");
        });

        return result;
    }

    /**
     * Closes the given document (which may or may not be the current document in the editor UI, and
     * may or may not be in the working set). Discards any unsaved changes if isDirty - it is
     * expected that the UI has already confirmed with the user before calling us.
     *
     * This will select a different file for editing if the document was the current one (if possible;
     * in some cases, the editor may be left blank instead). This will also remove the doc from the
     * working set if it was in the set.
     *
     * @param {!Document} document
     */
    function closeDocument(document) {
        // 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 == document) {
            var wsIndex = _findInWorkingSet(document.file);

            // Decide which doc to show in editor after this one
            var nextDocument;
            if (wsIndex == -1) {
                // If doc wasn't in working set, use bottommost working set item
                if (_workingSet.length > 0) {
                    nextDocument = _workingSet[ _workingSet.length  - 1 ];
                }
                // else: leave nextDocument null; editor area will be blank
            } else {
                // If doc was in working set, use item next to it (below if possible)
                if (wsIndex < _workingSet.length - 1) {
                    nextDocument = _workingSet[wsIndex + 1];
                } else if (wsIndex > 0) {
                    nextDocument = _workingSet[wsIndex - 1];
                }
                // else: leave nextDocument null; editor area will be blank
            }

            // Switch editor to next document (or blank it out)
            if (nextDocument)
                showInEditor(nextDocument);
            else
                _clearEditor();
        }

        // (Now we're guaranteed that the current document is not the one we're closing)
        console.assert(_currentDocument != document);

        // 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(document);

        // 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 closeDocument() for all Documents. Same caveat: this discards any
     * unsaved changes, so the UI should confirm with the user before calling this.
     */
    function closeAll() {
        // TODO: could be more efficient by clearing working set in bulk instead of via
        // individual notifications, and ensuring we don't switch editors while closing...

        var allDocs = _workingSet.slice(0);  //slice() to clone
        if (_currentDocument != null && allDocs.indexOf(_currentDocument) == -1)
            allDocs.push(_currentDocument);

        for (var i=0; i < allDocs.length; i++) {
            closeDocument( allDocs[i] );
        }
    }

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

        workingSet.forEach(function(value, index) {
            // flag the currently active editor
            isActive = (value === currentDoc);

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

        storage.files = files;
    }

    /**
     * @private
     * Initializes the working set.
     */
    function _init() {
        var prefs       = PreferencesManager.getPreferences(PREFERENCES_CLIENT_ID);

        if (prefs.files === undefined) {
            return;
        }

        var projectRoot = ProjectManager.getProjectRoot()
        ,   filesToOpen = []
        ,   activeFile;

        // in parallel, check if files exist
        // TODO (jasonsj): delay validation until user requests the editor (like Eclipse)?
        //                 e.g. A file to restore no longer exists. Should we silently ignore
        //                 it or let the user be notified when they attempt to open the Document?
        var result = (function() {
            var deferred        = new $.Deferred();
            var fileCount       = prefs.files.length
            ,   responseCount   = 0;

            function next() {
                responseCount++;

                if (responseCount == fileCount) {
                    deferred.resolve();
                }
            };

            prefs.files.forEach(function(value, index) {
                // check if the file still exists
                projectRoot.getFile(value.file, {}
                    , function(fileEntry) {
                        // maintain original sequence
                        filesToOpen[index] = fileEntry;

                        if (value.active) {
                            activeFile = fileEntry;
                        }

                        next();
                    }
                    , function(error) {
                        filesToOpen[index] = null;
                        next();
                    }
                );
            });

            return deferred;
        })();

        result.done(function() {
            var activeDoc
            ,   doc;

            // Add all existing files to the working set
            filesToOpen.forEach(function(value, index) {
                if (value) {
                    doc = new Document(value);
                    addToWorkingSet(doc);

                    if (value === activeFile) {
                        activeDoc = doc;
                    }
                }
            });

            // Initialize the active editor
            if(activeDoc == null && _workingSet.length > 0) {
                activeDoc = _workingSet[0]
            }

            if (activeDoc != null) {
                showInEditor(activeDoc);
            }
        });
    }

    // Define public API
    exports.Document = Document;
    exports.getCurrentDocument = getCurrentDocument;
    exports.getDocumentForFile = getDocumentForFile;
    exports.getWorkingSet = getWorkingSet;
    exports.showInEditor = showInEditor;
    exports.addToWorkingSet = addToWorkingSet;
    exports.closeDocument = closeDocument;
    exports.readAsText = readAsText;
    exports.closeAll = closeAll;

    // Register preferences callback
    PreferencesManager.addPreferencesClient(PREFERENCES_CLIENT_ID, _savePreferences, this);

    // Initialize after ProjectManager is loaded
    $(ProjectManager).on("isFirstProjectOpen", function(event, projectRoot) {
        _init();
    });
});


/* ==========================================================
 * bootstrap-twipsy.js v1.4.0
 * http://twitter.github.com/bootstrap/javascript.html#twipsy
 * Adapted from the original jQuery.tipsy by Jason Frame
 * ==========================================================
 * Copyright 2011 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ========================================================== */

!function( $ ) {

  "use strict"

 /* CSS TRANSITION SUPPORT (https://gist.github.com/373874)
  * ======================================================= */

  var transitionEnd

  $(document).ready(function () {

    $.support.transition = (function () {
      var thisBody = document.body || document.documentElement
        , thisStyle = thisBody.style
        , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined
      return support
    })()

    // set CSS transition event type
    if ( $.support.transition ) {
      transitionEnd = "TransitionEnd"
      if ( $.browser.webkit ) {
      	transitionEnd = "webkitTransitionEnd"
      } else if ( $.browser.mozilla ) {
      	transitionEnd = "transitionend"
      } else if ( $.browser.opera ) {
      	transitionEnd = "oTransitionEnd"
      }
    }

  })

 /* TWIPSY PUBLIC CLASS DEFINITION
  * ============================== */

  var Twipsy = function ( element, options ) {
    this.$element = $(element)
    this.options = options
    this.enabled = true
    this.fixTitle()
  }

  Twipsy.prototype = {

    show: function() {
      var pos
        , actualWidth
        , actualHeight
        , placement
        , $tip
        , tp

      if (this.hasContent() && this.enabled) {
        $tip = this.tip()
        this.setContent()

        if (this.options.animate) {
          $tip.addClass('fade')
        }

        $tip
          .remove()
          .css({ top: 0, left: 0, display: 'block' })
          .prependTo(document.body)

        pos = $.extend({}, this.$element.offset(), {
          width: this.$element[0].offsetWidth
        , height: this.$element[0].offsetHeight
        })

        actualWidth = $tip[0].offsetWidth
        actualHeight = $tip[0].offsetHeight

        placement = maybeCall(this.options.placement, this, [ $tip[0], this.$element[0] ])

        switch (placement) {
          case 'below':
            tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}
            break
          case 'above':
            tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}
            break
          case 'left':
            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset}
            break
          case 'right':
            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset}
            break
        }

        $tip
          .css(tp)
          .addClass(placement)
          .addClass('in')
      }
    }

  , setContent: function () {
      var $tip = this.tip()
      $tip.find('.twipsy-inner')[this.options.html ? 'html' : 'text'](this.getTitle())
      $tip[0].className = 'twipsy'
    }

  , hide: function() {
      var that = this
        , $tip = this.tip()

      $tip.removeClass('in')

      function removeElement () {
        $tip.remove()
      }

      $.support.transition && this.$tip.hasClass('fade') ?
        $tip.bind(transitionEnd, removeElement) :
        removeElement()
    }

  , fixTitle: function() {
      var $e = this.$element
      if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
        $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
      }
    }

  , hasContent: function () {
      return this.getTitle()
    }

  , getTitle: function() {
      var title
        , $e = this.$element
        , o = this.options

        this.fixTitle()

        if (typeof o.title == 'string') {
          title = $e.attr(o.title == 'title' ? 'data-original-title' : o.title)
        } else if (typeof o.title == 'function') {
          title = o.title.call($e[0])
        }

        title = ('' + title).replace(/(^\s*|\s*$)/, "")

        return title || o.fallback
    }

  , tip: function() {
      return this.$tip = this.$tip || $('
').html(this.options.template)
    }

  , validate: function() {
      if (!this.$element[0].parentNode) {
        this.hide()
        this.$element = null
        this.options = null
      }
    }

  , enable: function() {
      this.enabled = true
    }

  , disable: function() {
      this.enabled = false
    }

  , toggleEnabled: function() {
      this.enabled = !this.enabled
    }

  , toggle: function () {
      this[this.tip().hasClass('in') ? 'hide' : 'show']()
    }

  }

 /* TWIPSY PRIVATE METHODS
  * ====================== */

   function maybeCall ( thing, ctx, args ) {
     return typeof thing == 'function' ? thing.apply(ctx, args) : thing
   }

 /* TWIPSY PLUGIN DEFINITION
  * ======================== */

  $.fn.twipsy = function (options) {
    $.fn.twipsy.initWith.call(this, options, Twipsy, 'twipsy')
    return this
  }

  $.fn.twipsy.initWith = function (options, Constructor, name) {
    var twipsy
      , binder
      , eventIn
      , eventOut

    if (options === true) {
      return this.data(name)
    } else if (typeof options == 'string') {
      twipsy = this.data(name)
      if (twipsy) {
        twipsy[options]()
      }
      return this
    }

    options = $.extend({}, $.fn[name].defaults, options)

    function get(ele) {
      var twipsy = $.data(ele, name)

      if (!twipsy) {
        twipsy = new Constructor(ele, $.fn.twipsy.elementOptions(ele, options))
        $.data(ele, name, twipsy)
      }

      return twipsy
    }

    function enter() {
      var twipsy = get(this)
      twipsy.hoverState = 'in'

      if (options.delayIn == 0) {
        twipsy.show()
      } else {
        twipsy.fixTitle()
        setTimeout(function() {
          if (twipsy.hoverState == 'in') {
            twipsy.show()
          }
        }, options.delayIn)
      }
    }

    function leave() {
      var twipsy = get(this)
      twipsy.hoverState = 'out'
      if (options.delayOut == 0) {
        twipsy.hide()
      } else {
        setTimeout(function() {
          if (twipsy.hoverState == 'out') {
            twipsy.hide()
          }
        }, options.delayOut)
      }
    }

    if (!options.live) {
      this.each(function() {
        get(this)
      })
    }

    if (options.trigger != 'manual') {
      binder   = options.live ? 'live' : 'bind'
      eventIn  = options.trigger == 'hover' ? 'mouseenter' : 'focus'
      eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur'
      this[binder](eventIn, enter)[binder](eventOut, leave)
    }

    return this
  }

  $.fn.twipsy.Twipsy = Twipsy

  $.fn.twipsy.defaults = {
    animate: true
  , delayIn: 0
  , delayOut: 0
  , fallback: ''
  , placement: 'above'
  , html: false
  , live: false
  , offset: 0
  , title: 'title'
  , trigger: 'hover'
  , template: '

'
  }

  $.fn.twipsy.rejectAttrOptions = [ 'title' ]

  $.fn.twipsy.elementOptions = function(ele, options) {
    var data = $(ele).data()
      , rejects = $.fn.twipsy.rejectAttrOptions
      , i = rejects.length

    while (i--) {
      delete data[rejects[i]]
    }

    return $.extend({}, options, data)
  }

}( window.jQuery || window.ender );


/*!
 * Bootstrap v1.4.0
 *
 * Copyright 2011 Twitter, Inc
 * Licensed under the Apache License v2.0
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Designed and built with all the love in the world @twitter by @mdo and @fat.
 * Date: Thu Jan  5 07:14:51 PST 2012
 */

// CSS Reset
@import "reset.less";

// Core variables and mixins
@import "variables.less"; // Modify this for custom colors, font-sizes, etc
@import "mixins.less";

// Grid system and page structure
@import "scaffolding.less";

// Styled patterns and elements
@import "type.less";
@import "forms.less";
@import "tables.less";
@import "patterns.less";


This directory is marked read only when tests are run. Attempts to write files here should fail.


// jstree dblclick handling seems to steal focus from editor, so set focus again

var result = doOpenWithOptionalPath(fullPath);

keep keep keep replace replace keep keep keep keep keep


module Jasmine
  module Core
    VERSION = "1.1.0"
  end
end


/* Typography.less
 * Headings, body text, lists, code, and more for a versatile and durable typography system
 * ---------------------------------------------------------------------------------------- */

// BODY TEXT
// ---------

p {
  #font > .shorthand(normal,@basefont,@baseline);
  margin-bottom: @baseline / 2;
  small {
    font-size: @basefont - 2;
    color: @grayLight;
  }
}

// HEADINGS
// --------

h1, h2, h3, h4, h5, h6 {
  font-weight: bold;
  color: @grayDark;
  small {
    color: @grayLight;
  }
}
h1 {
  margin-bottom: @baseline;
  font-size: 30px;
  line-height: @baseline * 2;
  small {
    font-size: 18px;
  }
}
h2 {
  font-size: 24px;
  line-height: @baseline * 2;
  small {
    font-size: 14px;
  }
}
h3, h4, h5, h6 {
  line-height: @baseline * 2;
}
h3 {
  font-size: 18px;
  small {
    font-size: 14px;
  }
}
h4 {
  font-size: 16px;
  small {
    font-size: 12px;
  }
}
h5 {
  font-size: 14px;
}
h6 {
  font-size: 13px;
  color: @grayLight;
  text-transform: uppercase;
}

// COLORS
// ------

// Unordered and Ordered lists
ul, ol {
  margin: 0 0 @baseline 25px;
}
ul ul,
ul ol,
ol ol,
ol ul {
  margin-bottom: 0;
}
ul {
  list-style: disc;
}
ol {
  list-style: decimal;
}
li {
  line-height: @baseline;
  color: @gray;
}
ul.unstyled {
  list-style: none;
  margin-left: 0;
}

// Description Lists
dl {
  margin-bottom: @baseline;
  dt, dd {
    line-height: @baseline;
  }
  dt {
    font-weight: bold;
  }
  dd {
    margin-left: @baseline / 2;
  }
}

// MISC
// ----

// Horizontal rules
hr {
  margin: 20px 0 19px;
  border: 0;
  border-bottom: 1px solid #eee;
}

// Emphasis
strong {
  font-style: inherit;
  font-weight: bold;
}
em {
  font-style: italic;
  font-weight: inherit;
  line-height: inherit;
}
.muted {
  color: @grayLight;
}

// Blockquotes
blockquote {
  margin-bottom: @baseline;
  border-left: 5px solid #eee;
  padding-left: 15px;
  p {
    #font > .shorthand(300,14px,@baseline);
    margin-bottom: 0;
  }
  small {
    display: block;
    #font > .shorthand(300,12px,@baseline);
    color: @grayLight;
    &:before {
      content: '\2014 \00A0';
    }
  }
}

// Addresses
address {
  display: block;
  line-height: @baseline;
  margin-bottom: @baseline;
}

// Inline and block code styles
code, pre {
  padding: 0 3px 2px;
  font-family: Monaco, Andale Mono, Courier New, monospace;
  font-size: 12px;
  .border-radius(3px);
}
code {
  background-color: lighten(@orange, 40%);
  color: rgba(0,0,0,.75);
  padding: 1px 3px;
}
pre {
  background-color: #f5f5f5;
  display: block;
  padding: (@baseline - 1) / 2;
  margin: 0 0 @baseline;
  line-height: @baseline;
  font-size: 12px;
  border: 1px solid #ccc;
  border: 1px solid rgba(0,0,0,.15);
  .border-radius(3px);
  white-space: pre;
  white-space: pre-wrap;
  word-wrap: break-word;

}


var readFixtures = function() {
  return jasmine.getFixtures().proxyCallTo_('read', arguments);
};

var preloadFixtures = function() {
  jasmine.getFixtures().proxyCallTo_('preload', arguments);
};

var loadFixtures = function() {
  jasmine.getFixtures().proxyCallTo_('load', arguments);
};

var setFixtures = function(html) {
  jasmine.getFixtures().set(html);
};

var sandbox = function(attributes) {
  return jasmine.getFixtures().sandbox(attributes);
};

var spyOnEvent = function(selector, eventName) {
  jasmine.JQuery.events.spyOn(selector, eventName);
}

jasmine.getFixtures = function() {
  return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures();
};

jasmine.Fixtures = function() {
  this.containerId = 'jasmine-fixtures';
  this.fixturesCache_ = {};
  this.fixturesPath = 'spec/javascripts/fixtures';
};

jasmine.Fixtures.prototype.set = function(html) {
  this.cleanUp();
  this.createContainer_(html);
};

jasmine.Fixtures.prototype.preload = function() {
  this.read.apply(this, arguments);
};

jasmine.Fixtures.prototype.load = function() {
  this.cleanUp();
  this.createContainer_(this.read.apply(this, arguments));
};

jasmine.Fixtures.prototype.read = function() {
  var htmlChunks = [];

  var fixtureUrls = arguments;
  for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) {
    htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex]));
  }

  return htmlChunks.join('');
};

jasmine.Fixtures.prototype.clearCache = function() {
  this.fixturesCache_ = {};
};

jasmine.Fixtures.prototype.cleanUp = function() {
  jQuery('#' + this.containerId).remove();
};

jasmine.Fixtures.prototype.sandbox = function(attributes) {
  var attributesToSet = attributes || {};
  return jQuery('
').attr(attributesToSet);
};

jasmine.Fixtures.prototype.createContainer_ = function(html) {
  var container;
  if(html instanceof jQuery) {
    container = jQuery('
');
    container.html(html);
  } else {
    container = '
' + html + '
'
  }
  jQuery('body').append(container);
};

jasmine.Fixtures.prototype.getFixtureHtml_ = function(url) {
  if (typeof this.fixturesCache_[url] == 'undefined') {
    this.loadFixtureIntoCache_(url);
  }
  return this.fixturesCache_[url];
};

jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function(relativeUrl) {
  var self = this;
  var url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl;
  jQuery.ajax({
    async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded
    cache: false,
    dataType: 'html',
    url: url,
    success: function(data) {
      self.fixturesCache_[relativeUrl] = data;
    },
    error: function(jqXHR, status, errorThrown) {
        throw Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + errorThrown.message + ')');
    }
  });
};

jasmine.Fixtures.prototype.proxyCallTo_ = function(methodName, passedArguments) {
  return this[methodName].apply(this, passedArguments);
};

jasmine.JQuery = function() {};

jasmine.JQuery.browserTagCaseIndependentHtml = function(html) {
  return jQuery('
').append(html).html();
};

jasmine.JQuery.elementToString = function(element) {
  return jQuery('
').append(element.clone()).html();
};

jasmine.JQuery.matchersClass = {};

(function(namespace) {
  var data = {
    spiedEvents: {},
    handlers:    []
  };

  namespace.events = {
    spyOn: function(selector, eventName) {
      var handler = function(e) {
        data.spiedEvents[[selector, eventName]] = e;
      };
      jQuery(selector).bind(eventName, handler);
      data.handlers.push(handler);
    },

    wasTriggered: function(selector, eventName) {
      return !!(data.spiedEvents[[selector, eventName]]);
    },

    cleanUp: function() {
      data.spiedEvents = {};
      data.handlers    = [];
    }
  }
})(jasmine.JQuery);

(function(){
  var jQueryMatchers = {
    toHaveClass: function(className) {
      return this.actual.hasClass(className);
    },

    toBeVisible: function() {
      return this.actual.is(':visible');
    },

    toBeHidden: function() {
      return this.actual.is(':hidden');
    },

    toBeSelected: function() {
      return this.actual.is(':selected');
    },

    toBeChecked: function() {
      return this.actual.is(':checked');
    },

    toBeEmpty: function() {
      return this.actual.is(':empty');
    },

    toExist: function() {
      return this.actual.size() > 0;
    },

    toHaveAttr: function(attributeName, expectedAttributeValue) {
      return hasProperty(this.actual.attr(attributeName), expectedAttributeValue);
    },

    toHaveId: function(id) {
      return this.actual.attr('id') == id;
    },

    toHaveHtml: function(html) {
      return this.actual.html() == jasmine.JQuery.browserTagCaseIndependentHtml(html);
    },

    toHaveText: function(text) {
      if (text && jQuery.isFunction(text.test)) {
        return text.test(this.actual.text());
      } else {
        return this.actual.text() == text;
      }
    },

    toHaveValue: function(value) {
      return this.actual.val() == value;
    },

    toHaveData: function(key, expectedValue) {
      return hasProperty(this.actual.data(key), expectedValue);
    },

    toBe: function(selector) {
      return this.actual.is(selector);
    },

    toContain: function(selector) {
      return this.actual.find(selector).size() > 0;
    },

    toBeDisabled: function(selector){
      return this.actual.is(':disabled');
    },

    // tests the existence of a specific event binding
    toHandle: function(eventName) {
      var events = this.actual.data("events");
      return events && events[eventName].length > 0;
    },

    // tests the existence of a specific event binding + handler
    toHandleWith: function(eventName, eventHandler) {
      var stack = this.actual.data("events")[eventName];
      var i;
      for (i = 0; i < stack.length; i++) {
        if (stack[i].handler == eventHandler) {
          return true;
        }
      }
      return false;
    }
  };

  var hasProperty = function(actualValue, expectedValue) {
    if (expectedValue === undefined) {
      return actualValue !== undefined;
    }
    return actualValue == expectedValue;
  };

  var bindMatcher = function(methodName) {
    var builtInMatcher = jasmine.Matchers.prototype[methodName];

    jasmine.JQuery.matchersClass[methodName] = function() {
      if (this.actual instanceof jQuery) {
        var result = jQueryMatchers[methodName].apply(this, arguments);
        this.actual = jasmine.JQuery.elementToString(this.actual);
        return result;
      }

      if (builtInMatcher) {
        return builtInMatcher.apply(this, arguments);
      }

      return false;
    };
  };

  for(var methodName in jQueryMatchers) {
    bindMatcher(methodName);
  }
})();

beforeEach(function() {
  this.addMatchers(jasmine.JQuery.matchersClass);
  this.addMatchers({
    toHaveBeenTriggeredOn: function(selector) {
      this.message = function() {
        return [
          "Expected event " + this.actual + " to have been triggered on" + selector,
          "Expected event " + this.actual + " not to have been triggered on" + selector
        ];
      };
      return jasmine.JQuery.events.wasTriggered(selector, this.actual);
    }
  })
});

afterEach(function() {
  jasmine.getFixtures().cleanUp();
  jasmine.JQuery.events.cleanUp();
});


*    - isFirstProjectOpen -- When the ProjectManager initializes the first

*                            project at application start-up.

* These are jQuery events, so to listen for them you do something like this:

*    $(ProjectManager).on("eventname", handler);

keep keep keep replace keep keep keep keep

*    - initializeComplete -- When the ProjectManager initializes the first


/*
 * Copyright 2012 Adobe Systems Incorporated. All Rights Reserved.
 */

/**
 * PreferencesManager
 *
 */
define(function(require, exports, module) {
    var PREFERENCES_KEY = "com.adobe.brackets.preferences";

    // Private Properties
    var preferencesKey      = PREFERENCES_KEY
    ,   callbacks           = {}
    ,   prefStorage
    ,   persistentStorage;

    // Use localStorage by default
    _initStorage( localStorage );

    /**
     * Retrieves a copy of the client's preferences object.
     *
     * @param {string}  unique identifier clientID
     * @return {object} preference
     */
    function getPreferences( clientID ) {
        if ( ( clientID === undefined ) || ( clientID == null ) ) {
            throw new Error("Invalid clientID");
        }

        var prefs = prefStorage[ clientID ];

        if ( prefs === undefined )
            return undefined;

        // create a deep copy to return to the client
        return JSON.parse( JSON.stringify( prefs ) );
    }

    /**
     * Registers a save participant callback for a client. The callback is
     * fired when Brackets quits (window.unload). When fired, callbacks may
     * persist data (e.g. preferences or current state) as valid JSON values
     * to the storage argument.
     *
     * @param {string}          unique identifier clientID for this client
     * @param {function(...)}   callback function
     * @param {object}          optional "this" object for the callback
     * @param {object}          optional default preferences object for this client
     */
    function addPreferencesClient( clientID, callback, instance, defaults ) {
        if ( typeof callback !== "function" ) {
            throw new Error("Invalid arguments");
        }

        // attempt to load existing preferences
        var clientPrefs = getPreferences( clientID );

        // if clientPrefs is undefined, try defaults
        if ( clientPrefs === undefined ) {
            if ( JSON.stringify( defaults ) ) {
                // use defaults if it is a valid JSON object
                clientPrefs = defaults;
            }
            else {
                // use empty defaults if JSON validation fails
                clientPrefs = {};
            }

            // save defaults in-memory, storage
            prefStorage[ clientID ] = clientPrefs;
            saveToPersistentStorage();
        }

        var callbackData = { clientID: clientID
                           , callback: callback
                           , instance: instance };

        // add to callbacks list
        callbacks[ clientID ] = callbackData;
    }

    /**
     * Save all participants.
     */
    function savePreferences() {
        var data
        ,   storage;

        // iterate over all preference clients
        $.each( callbacks, function( index, value ) {
            data = callbacks[ index ];
            storage = getPreferences( data.clientID );

            // fire callback with thisArg and preference storage
            try {
                data.callback.call( data.instance, storage );
            }
            catch ( e ) {
                console.log( "PreferenceManager.savePreferences(): Failed to save data for clientID " + data.clientID );
            }

            // only save preferences that can be serialized with JSON
            if ( JSON.stringify( storage )) {
                prefStorage[ data.clientID ] = storage;
            }
        });

        saveToPersistentStorage();
    }

    /**
     * Saves to persistent storage.
     * TODO (jasonsj): local and/or hosted
     */
    function saveToPersistentStorage() {
        // save all preferences
        persistentStorage.setItem( preferencesKey, JSON.stringify( prefStorage ) );
    }

    /**
     * @private
     * Redirects preference storage to another key in persistent storage.
     * Allows unit test preferences to be stored in the same mechanism as
     * production preferences without clobbering.
     *
     * @return {string} Previous key
     */
    function _setStorageKey( key ) {
        var oldKey = preferencesKey;

        preferencesKey = key;

        // re-init in-memory prefs when changing keys
        _initStorage( persistentStorage );

        return oldKey;
    }

    /**
     * @private
     * Initialize persistent storage implementation
     */
    function _initStorage( storage ) {
        persistentStorage = storage;
        prefStorage = JSON.parse( persistentStorage.getItem( preferencesKey ) );

        // initialize empty preferences if none were found in storage
        if ( !prefStorage ) {
            _reset();
        }
    }

    /**
     * @private
     * Reset preferences and callbacks
     */
    function _reset() {
        callbacks = {};
        prefStorage = {};

        // Note that storage.clear() is not used. Production and unit test code
        // both rely on the same backing storage but unique item keys.
        persistentStorage.setItem( preferencesKey, JSON.stringify( prefStorage ) );
    }

    // Public API
    exports.getPreferences          = getPreferences;
    exports.addPreferencesClient    = addPreferencesClient;
    exports.savePreferences         = savePreferences;

    // Internal Use Only
    exports._reset                  = _reset;
    exports._setStorageKey          = _setStorageKey;
});


#!/bin/bash
BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

echo $BASEDIR

# open the Jasmine SpecRunner
open ${BASEDIR}/../../bin/mac/Brackets.app --args file:///${BASEDIR}/SpecRunner.html


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Jasmine Spec Runner</title>

  <link rel="stylesheet" type="text/css" href="../jasmine.css">
  <script type="text/javascript" src="../jasmine.js"></script>
  <script type="text/javascript" src="../jasmine-html.js"></script>

  <!-- include spec files here... -->
  <script type="text/javascript" src="spec/SpecHelper.js"></script>
  <script type="text/javascript" src="spec/PlayerSpec.js"></script>

  <!-- include source files here... -->
  <script type="text/javascript" src="src/Player.js"></script>
  <script type="text/javascript" src="src/Song.js"></script>

  <script type="text/javascript">
    (function() {
      var jasmineEnv = jasmine.getEnv();
      jasmineEnv.updateInterval = 1000;

      var trivialReporter = new jasmine.TrivialReporter();

      jasmineEnv.addReporter(trivialReporter);

      jasmineEnv.specFilter = function(spec) {
        return trivialReporter.specFilter(spec);
      };

      var currentWindowOnload = window.onload;

      window.onload = function() {
        if (currentWindowOnload) {
          currentWindowOnload();
        }
        execJasmine();
      };

      function execJasmine() {
        jasmineEnv.execute();
      }

    })();
  </script>

</head>

<body>
</body>
</html>


keep keep keep keep replace keep keep keep keep keep