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

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, $: false, brackets: false, FileError: false */

/**
 * 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 these events:
 *    - initializeComplete -- When the ProjectManager initializes the first
 *                            project at application start-up.
 *    - projectRootChanged -- when _projectRoot changes
 *
 * These are jQuery events, so to listen for them you do something like this:
 *    $(ProjectManager).on("eventname", handler);
 */
define(function (require, exports, module) {
    'use strict';

    // Load dependent non-module scripts
    require("thirdparty/jstree_pre1.0_fix_1/jquery.jstree");

    // Load dependent modules
    var NativeFileSystem    = require("file/NativeFileSystem").NativeFileSystem,
        PreferencesManager  = require("preferences/PreferencesManager"),
        DocumentManager     = require("document/DocumentManager"),
        CommandManager      = require("command/CommandManager"),
        Commands            = require("command/Commands"),
        Dialogs             = require("widgets/Dialogs"),
        Strings             = require("strings"),
        FileViewController  = require("project/FileViewController"),
        PerfUtils           = require("utils/PerfUtils"),
        ViewUtils           = require("utils/ViewUtils"),
        FileUtils           = require("file/FileUtils");

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

    /**
     * @private
     * @see getProjectRoot()
     */
    var _projectRoot = null;

    /**
     * @private
     * Used to initialize jstree state
     */
    var _projectInitialLoad = {
        previous        : [],   /* array of arrays containing full paths to open at each depth of the tree */
        id              : 0,    /* incrementing id */
        fullPathToIdMap : {}    /* mapping of fullPath to tree node id attr */
    };

    var _documentSelectionFocusChange = function () {
        var curDoc = DocumentManager.getCurrentDocument();
        if (curDoc
                && (FileViewController.getFileSelectionFocus() !== FileViewController.WORKING_SET_VIEW)) {
            $("#project-files-container li").is(function (index) {
                var entry = $(this).data("entry");

                if (entry && entry.fullPath === curDoc.file.fullPath && !_projectTree.jstree("is_selected", $(this))) {
                    //we don't want to trigger another selection change event, so manually deselect
                    //and select without sending out notifications
                    _projectTree.jstree("deselect_all");
                    _projectTree.jstree("select_node", $(this), false);
                    return true;
                }
                return false;
            });
        } else if (_projectTree !== null) {
            _projectTree.jstree("deselect_all");
        }
    };

    $(FileViewController).on("documentSelectionFocusChange", _documentSelectionFocusChange);

    /**
     * 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;
    }

    /**
     * Returns true if absPath lies within the project, false otherwise.
     * Does not support paths containing ".."
     */
    function isWithinProject(absPath) {
        return (absPath.indexOf(_projectRoot.fullPath) === 0);
    }
    /**
     * If absPath lies within the project, returns a project-relative path. Else returns absPath
     * unmodified.
     * Does not support paths containing ".."
     */
    function makeProjectRelativeIfPossible(absPath) {
        if (isWithinProject(absPath)) {
            return absPath.slice(_projectRoot.fullPath.length);
        }
        return absPath;
    }

    /**
     * @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,
            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, remove the trailing slash
                shortPath = fullPath.slice(projectPathLength, -1);

                // 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;
    }

    /**
     * @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", "sort"],
                    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" },
                    sort :  function (a, b) {
                        if (brackets.platform === "win") {
                            // Windows: prepend folder names with a '0' and file names with a '1' so folders are listed first
                            var a1 = ($(a).hasClass("jstree-leaf") ? "1" : "0") + this.get_text(a).toLowerCase(),
                                b1 = ($(b).hasClass("jstree-leaf") ? "1" : "0") + this.get_text(b).toLowerCase();
                            return (a1 > b1) ? 1 : -1;
                        } else {
                            return this.get_text(a).toLowerCase() > this.get_text(b).toLowerCase() ? 1 : -1;
                        }
                    }
                }
            )
            .bind(
                "select_node.jstree",
                function (event, data) {
                    var entry = data.rslt.obj.data("entry");
                    if (entry.isFile) {
                        FileViewController.openAndSelectDocument(entry.fullPath, "ProjectManager");
                    }
                }
            )
            .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   = [],
                            node        = null;

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

                            if (node) {
                                toOpenIds.push(node);
                            }
                        });

                        // specify nodes to open and load
                        data.inst.data.core.to_open = toOpenIds;
                        _projectTree.jstree("reload_nodes", false);
                    }
                    if (_projectInitialLoad.previous.length === 0) {
                        // resolve after all paths are opened
                        result.resolve();
                    }
                }
            )
            .bind(
                "loaded.jstree open_node.jstree close_node.jstree",
                function (event, data) {
                    ViewUtils.updateChildrenToParentScrollwidth($("#project-files-container"));
                }
            );

        // jstree has a default event handler for dblclick that attempts to clear the
        // global window selection (presumably because it doesn't want text within the tree
        // to be selected). This ends up messing up CodeMirror, and we don't need this anyway
        // since we've turned off user selection of UI text globally. So we just unbind it,
        // and add our own double-click handler here.
        // Filed this bug against jstree at https://github.com/vakata/jstree/issues/163
        _projectTree.bind("init.jstree", function () {
            _projectTree
                .unbind("dblclick.jstree")
                .bind("dblclick.jstree", function (event) {
                    var entry = $(event.target).closest("li").data("entry");
                    if (entry && entry.isFile) {
                        FileViewController.addToWorkingSetAndSelect(entry.fullPath);
                    }
                });
        });

        return result;
    }

    /** @param {Entry} entry File or directory to filter */
    function _shouldShowInTree(entry) {
        if (entry.name[0] === ".") {       // "." prefix is always hidden on Mac
            return false;
        }

        if (entry.name === "Thumbs.db") {  // "Thumbs.db" hidden file auto-generated by Win
            return false;
        }

        return true;
    }

    /**
     * @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,
            entryI;

        for (entryI = 0; entryI < entries.length; entryI++) {
            entry = entries[entryI];

            if (_shouldShowInTree(entry)) {
                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
     * 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, isProjectRoot = false;

        if (treeNode === -1) {
            // Special case: root of tree
            dirEntry = _projectRoot;
            isProjectRoot = true;
        } 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),
                    wasNodeOpen = false,
                    emptyDirectory = (subtreeJSON.length === 0);

                if (emptyDirectory) {
                    if (!isProjectRoot) {
                        wasNodeOpen = treeNode.hasClass("jstree-open");
                    } else {
                        // project root is a special case, add a placeholder
                        subtreeJSON.push({});
                    }
                }

                jsTreeCallback(subtreeJSON);

                if (!isProjectRoot && emptyDirectory) {
                    // If the directory is empty, force it to appear as an open or closed node.
                    // This is a workaround for issue #149 where jstree would show this node as a leaf.
                    var classToAdd = (wasNodeOpen) ? "jstree-closed" : "jstree-open";

                    treeNode.removeClass("jstree-leaf jstree-closed jstree-open")
                            .addClass(classToAdd);
                }
            },
            function (error) {
                Dialogs.showModalDialog(
                    Dialogs.DIALOG_ID_ERROR,
                    Strings.ERROR_LOADING_PROJECT,
                    Strings.format(Strings.READ_DIRECTORY_ENTRIES_ERROR, dirEntry.fullPath, error.code)
                );
            }
        );

    }

    /** Returns the full path to the default project folder. The path is currently the brackets src folder.
     * TODO: (issue #267): Brackets does not yet support operating when there is no project folder. This code will likely
     * not be needed when this support is added.
     * @private
     * @return {!string} fullPath reference
     */
    function _getDefaultProjectPath() {
        var loadedPath = window.location.pathname;
        var bracketsSrc = loadedPath.substr(0, loadedPath.lastIndexOf("/"));

        bracketsSrc = FileUtils.convertToNativePath(bracketsSrc);

        return bracketsSrc;
    }

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

            _projectInitialLoad.previous = prefs.projectTreeState;

            if (brackets.inBrowser) {
                // In browser: dummy folder tree (hardcoded in ProjectManager)
                rootPath = "DummyProject";
                $("#project-title").html(rootPath);
            }
        }

        PerfUtils.markStart("Load Project: " + rootPath);

        // Populate file tree as long as we aren't running in the browser
        if (!brackets.inBrowser) {
            // Point at a real folder structure on local disk
            NativeFileSystem.requestNativeFileSystem(rootPath,
                function (rootEntry) {
                    var projectRootChanged = (!_projectRoot || !rootEntry)
                        || _projectRoot.fullPath !== rootEntry.fullPath;

                    // Success!
                    _projectRoot = rootEntry;

                    // Set title
                    $("#project-title").html(_projectRoot.name);

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

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

                        if (isFirstProjectOpen) {
                            $(exports).triggerHandler("initializeComplete", _projectRoot);
                        }

                        if (projectRootChanged) {
                            $(exports).triggerHandler("projectRootChanged", _projectRoot);
                        }
                    });
                    resultRenderTree.fail(function () {
                        result.reject();
                    });
                    resultRenderTree.always(function () {
                        PerfUtils.addMeasurement("Load Project: " + rootPath);
                    });
                },
                function (error) {
                    Dialogs.showModalDialog(
                        Dialogs.DIALOG_ID_ERROR,
                        Strings.ERROR_LOADING_PROJECT,
                        Strings.format(
                            Strings.REQUEST_NATIVE_FILE_SYSTEM_ERROR,
                            rootPath,
                            error.code,
                            function () {
                                result.reject();
                            }
                        )
                    ).done(function () {
                        // The project folder stored in preference doesn't exist, so load the default
                        // project directory.
                        // TODO (issue #267): When Brackets supports having no project directory
                        // defined this code will need to change
                        return loadProject(_getDefaultProjectPath());
                    });
                }
                );
        }

        return result;
    }

    /**
     * Refresh the current project from disk.
     */
    function reloadProject() {
        // Update prefs so tree state doesn't change.
        // TODO (Issue #487): PreferencesManager only exposes a savePreferences() method to save
        // preferences across all clients, but here we only want to update our own prefs.
        PreferencesManager.savePreferences();

        // reset tree node id's
        _projectInitialLoad.id = 0;

        var prefs = PreferencesManager.getPreferences(PREFERENCES_CLIENT_ID),
            result = new $.Deferred(),
            resultRenderTree,
            rootPath = _projectRoot.fullPath;

        _projectInitialLoad.previous = prefs.projectTreeState;

        // Populate file tree as long as we aren't running in the browser
        if (!brackets.inBrowser) {

            result.done(function () {
                _documentSelectionFocusChange();
            });

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

                    resultRenderTree.done(function () {
                        result.resolve();
                    });
                    resultRenderTree.fail(function () {
                        result.reject();
                    });
                },
                function (error) {
                    Dialogs.showModalDialog(
                        Dialogs.DIALOG_ID_ERROR,
                        Strings.ERROR_LOADING_PROJECT,
                        Strings.format(
                            Strings.REQUEST_NATIVE_FILE_SYSTEM_ERROR,
                            rootPath,
                            error.code,
                            function () {
                                result.reject();
                            }
                        )
                    ).done(function () {
                        // The current project folder no longer exists, so prompt for another one.
                        NativeFileSystem.showOpenDialog(false, true, "Choose project folder", _projectRoot.fullPath, null,
                            function (files) {
                                // If length == 0, user canceled the dialog, so use default project
                                if (files.length === 0) {
                                    files[0] = _getDefaultProjectPath();
                                }
                                // Load the new project into the folder tree
                                loadProject(files[0]);
                            },
                            function (error) {
                                Dialogs.showModalDialog(
                                    Dialogs.DIALOG_ID_ERROR,
                                    Strings.ERROR_LOADING_PROJECT,
                                    Strings.format(Strings.OPEN_DIALOG_ERROR, error.code)
                                );
                            }
                            );
                    });
                }
                );
        }
    }

    /**
     * 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, { promptOnly: true })
            .done(function () {
                // Pop up a folder browse dialog
                NativeFileSystem.showOpenDialog(false, true, "Choose a folder", _projectRoot.fullPath, 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) {
                        Dialogs.showModalDialog(
                            Dialogs.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)
    }

    /**
     * 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) {
        var node                = null,
            selection           = _projectTree.jstree("get_selected"),
            selectionEntry      = null,
            position            = "inside",
            escapeKeyPressed    = false,
            result              = new $.Deferred(),
            wasNodeOpen         = true;

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

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

                var parent = $.jstree._reference(_projectTree)._get_parent(selection);

                if (typeof (parent.data) === "function") {
                    // get Entry from tree node
                    // note that the jstree root will return undefined
                    selectionEntry = parent.data("entry");
                } else {
                    // reset here. will be replaced with project root.
                    selectionEntry = null;
                }
            } else if (selectionEntry.isDirectory) {
                wasNodeOpen = selection.hasClass("jstree-open");
            }
        }

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

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

            function errorCleanup() {
                // TODO (issue #115): 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.
                var parent = data.inst._get_parent(data.rslt.obj);

                _projectTree.jstree("remove", data.rslt.obj);

                // Restore tree node state and styling when errors occur.
                // parent returns -1 when at the root
                if (parent && (parent !== -1)) {
                    var methodName = (wasNodeOpen) ? "open_node" : "close_node";
                    var classToAdd = (wasNodeOpen) ? "jstree-open" : "jstree-closed";

                    // This is a workaround for issue #149 where jstree would show this node as a leaf.
                    _projectTree.jstree(methodName, parent);
                    parent.removeClass("jstree-leaf jstree-closed jstree-open")
                          .addClass(classToAdd);
                }

                result.reject();
            }

            if (!escapeKeyPressed) {
                // Validate file name
                // TODO (issue #270): 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) {
                    Dialogs.showModalDialog(
                        Dialogs.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)) {
                            Dialogs.showModalDialog(
                                Dialogs.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);

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

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

        // TODO (issue #115): Need API to get tree node for baseDir.
        // In the meantime, pass null for node so new item is placed
        // relative to the selection
        node = selection;

        // Open the node before creating the new child
        _projectTree.jstree("open_node", node);

        // 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 (issue #277): 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;
    }

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

    // Initialize now
    (function () {

        var defaults = {
            projectPath:      _getDefaultProjectPath(), /* initialze to brackets source */
            projectTreeState: ""
        };
        PreferencesManager.addPreferencesClient(PREFERENCES_CLIENT_ID, _savePreferences, this, defaults);
    }());
});


// Size the widget height to the max between the editor content and the related rules list

var widgetHeight = Math.max(this.$relatedContainer.find(".related").height(), this.$editorsDiv.height());

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

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, $: false, CodeMirror: false */

define(function (require, exports, module) {
    'use strict';

    // Load dependent modules
    var DocumentManager     = require("document/DocumentManager"),
        HTMLUtils           = require("language/HTMLUtils"),
        CSSUtils            = require("language/CSSUtils"),
        EditorManager       = require("editor/EditorManager"),
        InlineTextEditor    = require("editor/InlineTextEditor").InlineTextEditor;

    /**
     * @constructor
     * @extends {InlineWidget}
     */
    function CSSInlineEditor(rules) {
        InlineTextEditor.call(this);
        this._rules = rules;
        this._selectedRuleIndex = -1;
    }
    CSSInlineEditor.prototype = new InlineTextEditor();
    CSSInlineEditor.prototype.constructor = CSSInlineEditor;
    CSSInlineEditor.prototype.parentClass = InlineTextEditor.prototype;
    CSSInlineEditor.prototype.$editorsDiv = null;
    CSSInlineEditor.prototype.$relatedContainer = null;
    CSSInlineEditor.prototype._updateRelatedContainerProxy = null;

    /**
     * @override
     * @param {!Editor} hostEditor  Outer Editor instance that inline editor will sit within.
     *
     */
    CSSInlineEditor.prototype.load = function (hostEditor) {
        this.parentClass.load.call(this, hostEditor);

        // Container to hold all editors
        var self = this,
            $ruleItem,
            $location;

        // Create DOM to hold editors and related list
        this.$editorsDiv = $(document.createElement('div')).addClass("inlineEditorHolder");

        // Outer container for border-left and scrolling
        this.$relatedContainer = $(document.createElement("div")).addClass("relatedContainer");

        // List "selection" highlight
        this.$selectedMarker = $(document.createElement("div")).appendTo(this.$relatedContainer).addClass("selection");

        // Inner container
        var $related = $(document.createElement("div")).appendTo(this.$relatedContainer).addClass("related");

        // Rule list
        var $ruleList = $(document.createElement("ul")).appendTo($related);

        // create rule list
        this._ruleItems = [];
        this._rules.forEach(function (rule, i) {
            $ruleItem = $(document.createElement("li")).appendTo($ruleList);
            $ruleItem.text(rule.selector + " ");
            $ruleItem.click(function () {
                self.setSelectedRule(i);
            });

            $location = $(document.createElement("span")).appendTo($ruleItem);
            $location.addClass("location");
            $location.text(rule.document.file.name + ":" + (rule.lineStart + 1));

            self._ruleItems.push($ruleItem);
        });

        // select the first rule
        self.setSelectedRule(0);

        // attach to main container
        this.$htmlContent.append(this.$editorsDiv).append(this.$relatedContainer);

        // initialize position based on the main #editorHolder
        setTimeout(function () {
            self._updateRelatedContainer();
        }, 0);

        this._updateRelatedContainerProxy = $.proxy(this._updateRelatedContainer, this);

        // Changes to the host editor should update the relatedContainer
        $(this.hostEditor).on("change.CSSInlineEditor", this._updateRelatedContainerProxy);

        // Since overflow-y is hidden on the CM scrollerElement, the scroll event is never fired.
        // Instead, we add a hook to CM's onScroll to reposition the relatedContainer.
        this.hostEditor._codeMirror.setOption("onScroll", this._updateRelatedContainerProxy);

        return (new $.Deferred()).resolve();
    };

    /**
     *
     *
     */
    CSSInlineEditor.prototype.setSelectedRule = function (index) {
        var newIndex = Math.min(Math.max(0, index), this._rules.length - 1);

        this._selectedRuleIndex = newIndex;
        var $ruleItem = this._ruleItems[this._selectedRuleIndex];

        this._ruleItems[this._selectedRuleIndex].toggleClass("selected", true);

        // Remove previous editors
        this.editors.forEach(function (editor) {
            editor.destroy(); //release ref on Document
        });
        this.editors = [];
        this.$editorsDiv.children().remove();
        $(this.editors[0]).off("change.CSSInlineEditor");

        // Keyboard shortcuts
        var extraKeys = {
            "Alt-Up" : $.proxy(this.previousRule, this),
            "Alt-Down" : $.proxy(this.nextRule, this)
        };

        // Add new editor
        var rule = this.getSelectedRule();
        this.createInlineEditorFromText(rule.document, rule.lineStart, rule.lineEnd, this.$editorsDiv.get(0), extraKeys);

        // Changes in size to the inline editor should update the relatedContainer
        $(this.editors[0]).on("change.CSSInlineEditor", this._updateRelatedContainerProxy);

        this.sizeInlineWidgetToContents(true);
        this._updateRelatedContainer();

        // scroll the selection to the ruleItem, use setTimeout to wait for DOM updates
        var self = this;
        setTimeout(function () {
            var itemTop = $ruleItem.position().top;
            self.$selectedMarker.css("top", itemTop);
            self.$selectedMarker.height($ruleItem.height());

            // FUTURE (jasonsj): figure out if rule list should scroll
            itemTop -=  $ruleItem.parent().css("paddingTop").replace("px", "");
            self.$relatedContainer.scrollTop(itemTop);
        }, 0);
    };

    /**
     * Called any time inline was closed, whether manually (via closeThisInline()) or automatically
     */
    CSSInlineEditor.prototype.onClosed = function () {
        this.parentClass.onClosed.call(this); // super.onClosed()

        // remove resize handlers for relatedContainer
        $(this.hostEditor).off("change.CSSInlineEditor");
        $(this.editors[0]).off("change.CSSInlineEditor");
        this.hostEditor._codeMirror.setOption("onScroll", null);
    };

    /**
     *
     *
     */
    CSSInlineEditor.prototype._updateRelatedContainer = function () {
        var borderThickness = (this.$htmlContent.outerHeight() - this.$htmlContent.innerHeight()) / 2;
        this.$relatedContainer.css("top", this.$htmlContent.offset().top + borderThickness);
        this.$relatedContainer.height(this.$htmlContent.height());
    };

    /**
     *
     *
     */
    CSSInlineEditor.prototype.getRules = function () {
        return this._rules;
    };

    /**
     *
     *
     */
    CSSInlineEditor.prototype.getSelectedRule = function () {
        return this._rules[this._selectedRuleIndex];
    };

    /**
     * Display the next css rule in the rule list
     */
    CSSInlineEditor.prototype.nextRule = function () {
        this.setSelectedRule(this._selectedRuleIndex + 1);
    };

    /**
     *  Display the previous css rule in the rule list
     */
    CSSInlineEditor.prototype.previousRule = function () {
        this.setSelectedRule(this._selectedRuleIndex - 1);
    };

    /**
     * Sizes the inline widget height to be the maximum between the rule list height and the editor height
     * @overide
     */
    CSSInlineEditor.prototype.sizeInlineWidgetToContents = function (force) {
        this.parentClass.sizeInlineWidgetToContents.call(this, force);
        var widgetHeight = Math.max(this.$relatedContainer.height(), this.$editorsDiv.height());

        this.hostEditor.setInlineWidgetHeight(this.inlineId, widgetHeight, true);
    };

    /**
     * Given a position in an HTML editor, returns the relevant selector for the attribute/tag
     * surrounding that position, or "" if none is found.
     * @param {!Editor} editor
     * @private
     */
    function _getSelectorName(editor, pos) {
        var tagInfo = HTMLUtils.getTagInfo(editor, pos),
            selectorName = "";

        if (tagInfo.position.tokenType === HTMLUtils.TAG_NAME) {
            // Type selector
            selectorName = tagInfo.tagName;
        } else if (tagInfo.position.tokenType === HTMLUtils.ATTR_NAME ||
                   tagInfo.position.tokenType === HTMLUtils.ATTR_VALUE) {
            if (tagInfo.attr.name === "class") {
                // Class selector. We only look for the class name
                // that includes the insertion point. For example, if
                // the attribute is:
                //   class="error-dialog modal hide"
                // and the insertion point is inside "modal", we want ".modal"
                var attributeValue = tagInfo.attr.value;
                var startIndex = attributeValue.substr(0, tagInfo.position.offset).lastIndexOf(" ");
                var endIndex = attributeValue.indexOf(" ", tagInfo.position.offset);
                selectorName = "." +
                    attributeValue.substring(
                        startIndex === -1 ? 0 : startIndex + 1,
                        endIndex === -1 ? attributeValue.length : endIndex
                    );

                // If the insertion point is surrounded by space, selectorName is "."
                // Check for that here
                if (selectorName === ".") {
                    selectorName = "";
                }
            } else if (tagInfo.attr.name === "id") {
                // ID selector
                selectorName = "#" + tagInfo.attr.value;
            }
        }

        return selectorName;
    }

    /**
     * This function is registered with EditManager as an inline editor provider. It creates a CSSInlineEditor
     * when cursor is on an HTML tag name, class attribute, or id attribute, find associated
     * CSS rules and show (one/all of them) in an inline editor.
     *
     * @param {!Editor} editor
     * @param {!{line:Number, ch:Number}} pos
     * @return {$.Promise} a promise that will be resolved with an InlineWidget
     *      or null if we're not going to provide anything.
     */
    function htmlToCSSProvider(hostEditor, pos) {
        // Only provide a CSS editor when cursor is in HTML content
        if (hostEditor._codeMirror.getOption("mode") !== "htmlmixed") {
            return null;
        }
        var htmlmixedState = hostEditor._codeMirror.getTokenAt(pos).state;
        if (htmlmixedState.mode !== "html") {
            return null;
        }

        // Only provide CSS editor if the selection is an insertion point
        var sel = hostEditor.getSelection(false);
        if (sel.start.line !== sel.end.line || sel.start.ch !== sel.end.ch) {
            return null;
        }

        var selectorName = _getSelectorName(hostEditor, pos);
        if (selectorName === "") {
            return null;
        }

        var result = new $.Deferred();

        CSSUtils.findMatchingRules(selectorName)
            .done(function (rules) {
                if (rules && rules.length > 0) {
                    var cssInlineEditor = new CSSInlineEditor(rules);

                    cssInlineEditor.load(hostEditor).done(function () {
                        result.resolve(cssInlineEditor);
                    }).fail(function () {
                        result.reject();
                    });
                } else {
                    // No matching rules were found.
                    result.reject();
                }
            })
            .fail(function () {
                console.log("Error in findMatchingRules()");
                result.reject();
            });

        return result.promise();
    }

    EditorManager.registerInlineEditProvider(htmlToCSSProvider);

    exports.CSSInlineEditor = CSSInlineEditor;

});


/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, describe: false, it: false, expect: false, beforeEach: false, afterEach: false, waitsFor: false, runs: false */
define(function (require, exports, module) {
    'use strict';

    // Load dependent modules
    var PreferencesManager      = require("preferences/PreferencesManager"),
        SpecRunnerUtils         = require("./SpecRunnerUtils.js");

    var CLIENT_ID = "PreferencesManager-test";

    describe("PreferencesManager", function () {

        beforeEach(function () {
            // SpecRunner.js already initializes the unit test instance of
            // PreferencesManager to use the unit test key. All we need to do
            // here is reset to clear callbacks and in-memory preferences.
            PreferencesManager._reset();
        });

        it("should register clients", function () {
            var pass1 = false,
                pass2 = false;

            function callback1(storage) {
                pass1 = true;
            }
            function callback2(storage) {
                pass2 = true;
            }

            PreferencesManager.addPreferencesClient("client1", callback1);
            PreferencesManager.addPreferencesClient("client2", callback2);
            PreferencesManager.savePreferences();

            expect(pass1 && pass2).toBeTruthy();
        });

        it("should assign 'this' object in callback", function () {
            var pass = false;
            var thisObj = {
                prop : "value",
                callback : function (storage) {
                    pass = (this.prop === "value");
                }
            };

            PreferencesManager.addPreferencesClient(CLIENT_ID, thisObj.callback, thisObj);
            PreferencesManager.savePreferences();

            expect(pass).toBeTruthy();
        });

        it("should register clients without duplicates", function () {
            var count = 0;

            function callback(storage) {
                count++;
            }

            // register listener twice
            PreferencesManager.addPreferencesClient(CLIENT_ID, callback);
            PreferencesManager.addPreferencesClient(CLIENT_ID, callback);

            PreferencesManager.savePreferences();

            expect(count).toEqual(1);
        });

        it("should use default preferences", function () {
            var pass = false,
                defaults = { prop : "default value" };

            PreferencesManager.addPreferencesClient(CLIENT_ID, function () {}, null, defaults);
            var prefs = PreferencesManager.getPreferences(CLIENT_ID);

            expect(prefs.prop).toEqual("default value");
        });

        it("should return undefined prefs for unregistered clients", function () {
            var pass = (undefined === PreferencesManager.getPreferences(CLIENT_ID));
            expect(pass).toBeTruthy();
        });

        it("should fail when storing an invalid JSON object", function () {
            // define a callback that stores a function value
            function callback(storage) {
                storage.badJSON = function () {};
            }

            PreferencesManager.addPreferencesClient(CLIENT_ID, callback);
            PreferencesManager.savePreferences();

            // get prefs and confirm badJSON was never persisted
            var actual = PreferencesManager.getPreferences(CLIENT_ID);

            expect(actual.badJSON).toEqual(undefined);
        });

        it("should only write to preferences during save", function () {
            var fooUndefined = false,
                fooValue     = null;

            function callback(storage) {
                // storage.foo should not exist
                if (storage.foo === undefined) {
                    fooUndefined = true;
                } else {
                    // record previous value of foo
                    fooUndefined = false;
                    fooValue = storage.foo;
                }

                storage.foo = "callback";
            }

            PreferencesManager.addPreferencesClient(CLIENT_ID, callback);

            // try to modify preferences outside of savePreferences
            var before = PreferencesManager.getPreferences(CLIENT_ID);
            before.foo = "before";

            // storage.foo = "callback"
            PreferencesManager.savePreferences();

            var after1 = PreferencesManager.getPreferences(CLIENT_ID);

            expect(fooUndefined).toBeTruthy();
            expect(fooValue).toEqual(null);
            expect(after1.foo).toEqual("callback");

            // try to modify preferences copy from getPreferences()
            after1.foo = "after";

            // save again, storage.foo = "callback"
            PreferencesManager.savePreferences();

            var after2 = PreferencesManager.getPreferences(CLIENT_ID);

            // foo should still be callback, after1.foo = "after" as no effect
            expect(fooUndefined).toBeFalsy();
            expect(fooValue).toEqual("callback");
            expect(after2.foo).toEqual("callback");
        });
    });
});


This directory contains extensions that ship with brackets and are enabled
by default. Examples may include things like QuickFileOpen.

(README also serves as a dummy file so this directory can be added to git.)


// The related rules container size itself based on htmlContent which is set by setInlineWidgetHeight above.


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

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define: false, PathUtils: false, FileError: false, brackets: false */

/**
 * Set of utilites for working with the code editor
 */
define(function (require, exports, module) {
    'use strict';

    require("thirdparty/path-utils/path-utils.min");
    require("thirdparty/CodeMirror2/mode/xml/xml");
    require("thirdparty/CodeMirror2/mode/javascript/javascript");
    require("thirdparty/CodeMirror2/mode/css/css");
    require("thirdparty/CodeMirror2/mode/less/less");
    require("thirdparty/CodeMirror2/mode/htmlmixed/htmlmixed");

    /**
     * @private
     * Given a file URL, determines the mode to use based
     * off the file's extension.
     * @param {string} fileUrl  A cannonical file URL to extract the extension from
     */
    function getModeFromFileExtension(fileUrl) {
        var ext = PathUtils.filenameExtension(fileUrl);
        //incase the arg is just the ext
        if (!ext) {
            ext = fileUrl;
        }
        if (ext.charAt(0) === '.') {
            ext = ext.substr(1);
        }

        switch (ext) {

        case "js":
            return "javascript";

        case "json":
            return {name: "javascript", json: true};

        case "css":
            return "css";

        case "less":
            return "less";

        case "html":
        case "htm":
            return "htmlmixed";

        default:
            console.log("Called EditorUtils.js _getModeFromFileExtensions with an unhandled file extension: " + ext);
            return "";
        }
    }

    // Define public API
    exports.getModeFromFileExtension = getModeFromFileExtension;
});


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

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, $: false, CodeMirror: false */

/**
 * Editor is a 1-to-1 wrapper for a CodeMirror editor instance. It layers on Brackets-specific
 * functionality and provides APIs that cleanly pass through the bits of CodeMirror that the rest
 * of our codebase may want to interact with. An Editor is always backed by a Document, and stays
 * in sync with its content; because Editor keeps the Document alive, it's important to always
 * destroy() an Editor that's going away so it can release its Document ref.
 *
 * For now, there's a distinction between the "master" Editor for a Document - which secretly acts
 * as the Document's internal model of the text state - and the multitude of "slave" secondary Editors
 * which, via Document, sync their changes to and from that master.
 *
 * For now, direct access to the underlying CodeMirror object is still possible via _codeMirror --
 * but this is considered deprecated and may go away.
 *
 * The Editor object dispatches the following events:
 *    - keyEvent -- When any key event happens in the editor (whether it changes the text or not).
 *          Event handlers are passed ({Editor}, {KeyboardEvent}). The 2nd arg is the raw DOM event.
 *          Note: most listeners will only want to respond when event.type === "keypress".
 *    - lostContent -- When the backing Document changes in such a way that this Editor is no longer
 *          able to display accurate text. This occurs if the Document's file is deleted, or in certain
 *          Document->editor syncing edge cases that we do not yet support (the latter cause will
 *          eventually go away).
 *
 * The Editor also dispatches "change" events internally, but you should listen for those on
 * Documents, not Editors.
 *
 * These are jQuery events, so to listen for them you do something like this:
 *    $(editorInstance).on("eventname", handler);
 */
define(function (require, exports, module) {
    'use strict';

    var EditorManager    = require("editor/EditorManager");

    /**
     * @private
     * Handle Tab key press.
     * @param {!CodeMirror} instance CodeMirror instance.
     */
    function _handleTabKey(instance) {
        // Tab key handling is done as follows:
        // 1. If the selection is before any text and the indentation is to the left of
        //    the proper indentation then indent it to the proper place. Otherwise,
        //    add another tab. In either case, move the insertion point to the
        //    beginning of the text.
        // 2. If the selection is after the first non-space character, and is not an
        //    insertion point, indent the entire line(s).
        // 3. If the selection is after the first non-space character, and is an
        //    insertion point, insert a tab character or the appropriate number
        //    of spaces to pad to the nearest tab boundary.
        var from = instance.getCursor(true),
            to = instance.getCursor(false),
            line = instance.getLine(from.line),
            indentAuto = false,
            insertTab = false;

        if (from.line === to.line) {
            if (line.search(/\S/) > to.ch || to.ch === 0) {
                indentAuto = true;
            }
        }

        if (indentAuto) {
            var currentLength = line.length;
            CodeMirror.commands.indentAuto(instance);
            // If the amount of whitespace didn't change, insert another tab
            if (instance.getLine(from.line).length === currentLength) {
                insertTab = true;
                to.ch = 0;
            }
        } else if (instance.somethingSelected()) {
            CodeMirror.commands.indentMore(instance);
        } else {
            insertTab = true;
        }

        if (insertTab) {
            if (instance.getOption("indentWithTabs")) {
                CodeMirror.commands.insertTab(instance);
            } else {
                var i, ins = "", numSpaces = instance.getOption("tabSize");
                numSpaces -= to.ch % numSpaces;
                for (i = 0; i < numSpaces; i++) {
                    ins += " ";
                }
                instance.replaceSelection(ins, "end");
            }
        }
    }

    /**
     * @private
     * Handle left arrow, right arrow, backspace and delete keys when soft tabs are used.
     * @param {!CodeMirror} instance CodeMirror instance
     * @param {number} direction Direction of movement: 1 for forward, -1 for backward
     * @param {function} functionName name of the CodeMirror function to call
     * @return {boolean} true if key was handled
     */
    function _handleSoftTabNavigation(instance, direction, functionName) {
        var handled = false;
        if (!instance.getOption("indentWithTabs")) {
            var cursor = instance.getCursor(),
                tabSize = instance.getOption("tabSize"),
                jump = cursor.ch % tabSize,
                line = instance.getLine(cursor.line);

            if (direction === 1) {
                jump = tabSize - jump;

                if (cursor.ch + jump > line.length) { // Jump would go beyond current line
                    return false;
                }

                if (line.substr(cursor.ch, jump).search(/\S/) === -1) {
                    instance[functionName](jump, "char");
                    handled = true;
                }
            } else {
                // Quick exit if we are at the beginning of the line
                if (cursor.ch === 0) {
                    return false;
                }

                // If we are on the tab boundary, jump by the full amount,
                // but not beyond the start of the line.
                if (jump === 0) {
                    jump = tabSize;
                }

                // Search backwards to the first non-space character
                var offset = line.substr(cursor.ch - jump, jump).search(/\s*$/g);

                if (offset !== -1) { // Adjust to jump to first non-space character
                    jump -= offset;
                }

                if (jump > 0) {
                    instance[functionName](-jump, "char");
                    handled = true;
                }
            }
        }

        return handled;
    }

    /**
     * Checks if the user just typed a closing brace/bracket/paren, and considers automatically
     * back-indenting it if so.
     */
    function _checkElectricChars(jqEvent, editor, event) {
        var instance = editor._codeMirror;
        if (event.type === "keypress") {
            var keyStr = String.fromCharCode(event.which || event.keyCode);
            if (/[\]\}\)]/.test(keyStr)) {
                // If the whole line is whitespace, auto-indent it
                var lineNum = instance.getCursor().line;
                var lineStr = instance.getLine(lineNum);

                if (!/\S/.test(lineStr)) {
                    // Need to do the auto-indent on a timeout to ensure
                    // the keypress is handled before auto-indenting.
                    // This is the same timeout value used by the
                    // electricChars feature in CodeMirror.
                    setTimeout(function () {
                        instance.indentLine(lineNum);
                    }, 75);
                }
            }
        }
    }

    /** Launches CodeMirror's basic Find-within-single-editor feature */
    function _launchFind(codeMirror) {
        // Bring up CodeMirror's existing search bar UI
        codeMirror.execCommand("find");

        // Prepopulate the search field with the current selection, if any
        var findBarTextField = $(".CodeMirror-dialog input[type='text']");
        findBarTextField.attr("value", codeMirror.getSelection());
        findBarTextField.get(0).select();
    }

    /**
     * Creates a new CodeMirror editor instance bound to the given Document. The Document need not have
     * a "master" Editor realized yet, even if makeMasterEditor is false; in that case, the first time
     * an edit occurs we will automatically ask EditorManager to create a "master" editor to render the
     * Document modifiable.
     *
     * ALWAYS call destroy() when you are done with an Editor - otherwise it will leak a Document ref.
     *
     * @param {!Document} document
     * @param {!boolean} makeMasterEditor  If true, this Editor will set itself as the (secret) "master"
     *          Editor for the Document. If false, this Editor will attach to the Document as a "slave"/
     *          secondary editor.
     * @param {!string} mode  Syntax-highlighting language mode; "" means plain-text mode.
     *          See {@link EditorUtils#getModeFromFileExtension()}.
     * @param {!jQueryObject} container  Container to add the editor to.
     * @param {!Object<string, function(Editor)>} additionalKeys  Mapping of keyboard shortcuts to
     *          custom handler functions. Mapping is in CodeMirror format, NOT in our KeyMap format.
     * @param {{startLine: number, endLine: number}=} range If specified, range of lines within the document
     *          to display in this editor. Inclusive.
     */
    function Editor(document, makeMasterEditor, mode, container, additionalKeys, range) {
        var self = this;

        // Attach to document: add ref & handlers
        this.document = document;
        document.addRef();
        // store this-bound version of listeners so we can remove them later
        this._handleDocumentChange = this._handleDocumentChange.bind(this);
        this._handleDocumentDeleted = this._handleDocumentDeleted.bind(this);
        $(document).on("change", this._handleDocumentChange);
        $(document).on("deleted", this._handleDocumentDeleted);

        // (if makeMasterEditor, we attach the Doc back to ourselves below once we're fully initialized)

        this._inlineWidgets = [];

        // Editor supplies some standard keyboard behavior extensions of its own
        var codeMirrorKeyMap = {
            "Tab"  : _handleTabKey,
            "Left" : function (instance) {
                if (!_handleSoftTabNavigation(instance, -1, "moveH")) {
                    CodeMirror.commands.goCharLeft(instance);
                }
            },
            "Right" : function (instance) {
                if (!_handleSoftTabNavigation(instance, 1, "moveH")) {
                    CodeMirror.commands.goCharRight(instance);
                }
            },
            "Backspace" : function (instance) {
                if (!_handleSoftTabNavigation(instance, -1, "deleteH")) {
                    CodeMirror.commands.delCharLeft(instance);
                }
            },
            "Delete" : function (instance) {
                if (!_handleSoftTabNavigation(instance, 1, "deleteH")) {
                    CodeMirror.commands.delCharRight(instance);
                }
            },
            "Ctrl-F": _launchFind,
            "Cmd-F": _launchFind,
            "F3": "findNext",
            "Shift-F3": "findPrev",
            "Ctrl-H": "replace",
            "Shift-Delete": "cut",
            "Ctrl-Insert": "copy",
            "Shift-Insert": "paste"
        };

        EditorManager.mergeExtraKeys(self, codeMirrorKeyMap, additionalKeys);

        // We'd like null/"" to mean plain text mode. CodeMirror defaults to plaintext for any
        // unrecognized mode, but it complains on the console in that fallback case: so, convert
        // here so we're always explicit, avoiding console noise.
        if (!mode) {
            mode = "text/plain";
        }

        // Create the CodeMirror instance
        // (note: CodeMirror doesn't actually require using 'new', but jslint complains without it)
        this._codeMirror = new CodeMirror(container, {
            electricChars: false,   // we use our own impl of this to avoid CodeMirror bugs; see _checkElectricChars()
            indentUnit : 4,
            lineNumbers: true,
            matchBrackets: true,
            extraKeys: codeMirrorKeyMap
        });

        this._installEditorListeners();

        $(this)
            .on("keyEvent", _checkElectricChars)
            .on("change", this._handleEditorChange.bind(this));

        // Set code-coloring mode BEFORE populating with text, to avoid a flash of uncolored text
        this._codeMirror.setOption("mode", mode);

        // Initially populate with text. This will send a spurious change event, so need to make
        // sure this is understood as a 'sync from document' case, not a genuine edit
        this._duringSync = true;
        this._resetText(document.getText());
        this._duringSync = false;

        if (range) {
            // Hide all lines other than those we want to show. We do this rather than trimming the
            // text itself so that the editor still shows accurate line numbers.
            this._codeMirror.operation(function () {
                var i;
                for (i = 0; i < range.startLine; i++) {
                    self.hideLine(i);
                }
                var lineCount = self.lineCount();
                for (i = range.endLine + 1; i < lineCount; i++) {
                    self.hideLine(i);
                }
            });
            this._visibleRange = range;
            this.setCursorPos(range.startLine, 0);
        }

        // Now that we're fully initialized, we can point the document back at us if needed
        if (makeMasterEditor) {
            document._makeEditable(this);
        }
    }

    /**
     * Removes this editor from the DOM and detaches from the Document. If this is the "master"
     * Editor that is secretly providing the Document's backing state, then the Document reverts to
     * a read-only string-backed mode.
     */
    Editor.prototype.destroy = function () {
        // CodeMirror docs for getWrapperElement() say all you have to do is "Remove this from your
        // tree to delete an editor instance."
        $(this._codeMirror.getWrapperElement()).remove();

        // Disconnect from Document
        this.document.releaseRef();
        $(this.document).off("change", this._handleDocumentChange);
        $(this.document).off("deleted", this._handleDocumentDeleted);

        if (this.document._masterEditor === this) {
            this.document._makeNonEditable();
        }

        // Destroying us destroys any inline widgets we're hosting. Make sure their closeCallbacks
        // run, at least, since they may also need to release Document refs
        this._inlineWidgets.forEach(function (inlineInfo) {
            inlineInfo.closeCallback();
        });
    };

    Editor.prototype._applyChangesToEditor = function (editor, changeList) {
        // FUTURE: Technically we should add a replaceRange() method to Document and go through
        // that instead of talking to the given editor directly. However, we need to access
        // a CodeMirror API to make sure that the edits get batched properly, and it's not clear
        // that we want that exact API exposed in Document yet. So for now we just talk to
        // the editor directly. Eventually we will factor this out into a model API once we
        // have an actual central model.
        var cm = editor._codeMirror;
        cm.operation(function () {
            var change, newText;
            for (change = changeList; change; change = change.next) {
                newText = change.text.join('\n');
                if (!change.from || !change.to) {
                    if (change.from || change.to) {
                        console.log("Editor._applyChangesToEditor(): Change record received with only one end undefined--replacing entire text");
                    }
                    cm.setValue(newText);

                    // The editor's visible range is no longer meaningful since the entire text was replaced.
                    editor._visibleRange = null;
                } else {
                    cm.replaceRange(newText, change.from, change.to);

                    // If the editor is restricted to a specific visible range, update the visible range
                    // end points if any content was added before them, and hide any new text that's outside
                    // the visible range. Note that we don't rely on line handles for this since we
                    // want to gracefully handle cases where the start or end line was deleted during a change.
                    var range = editor._visibleRange;
                    if (range) {
                        var i, numAdded = change.text.length - (change.to.line - change.from.line + 1);
                        // Edits that cross into the first line need to cause an adjustment, but edits that
                        // are fully within the first line don't.
                        if (change.from.line !== range.startLine && change.to.line <= range.startLine) {
                            range.startLine += numAdded;
                        }
                        if (change.to.line <= range.endLine) {
                            range.endLine += numAdded;
                        }
                        for (i = change.from.line; i < change.from.line + change.text.length; i++) {
                            if (i < range.startLine || i > range.endLine) {
                                editor.hideLine(i);
                            }
                        }
                        //console.log("new visible range: " + editor._visibleRange.startLine + " - " + editor._visibleRange.endLine);
                        // TODO: should double-check that the range of non-hidden lines after this matches up
                        // with what we think _visibleRange is
                    }
                }
            }
        });
    };

    /**
     * Responds to changes in the CodeMirror editor's text, syncing the changes to the Document.
     * There are several cases where we want to ignore a CodeMirror change:
     *  - if we're the master editor, editor changes can be ignored because Document is already listening
     *    for our changes
     *  - if we're a secondary editor, editor changes should be ignored if they were caused by us reacting
     *    to a Document change
     */
    Editor.prototype._handleEditorChange = function (event, editor, changeList) {
        // we're currently syncing from the Document, so don't echo back TO the Document
        if (this._duringSync) {
            return;
        }

        // Secondary editor: force creation of "master" editor backing the model, if doesn't exist yet
        if (!this.document._masterEditor) {
            EditorManager._createFullEditorForDocument(this.document);
        }

        if (this.document._masterEditor !== this) {
            // Secondary editor:
            // we're not the ground truth; if we got here, this was a real editor change (not a
            // sync from the real ground truth), so we need to sync from us into the document
            // (which will directly push the change into the master editor).
            this._duringSync = true;
            this._applyChangesToEditor(this.document._masterEditor, changeList);
            this._duringSync = false;
        }
        // Else, Master editor:
        // we're the ground truth; nothing else to do, since everyone else will sync from us
        // note: this change might have been a real edit made by the user, OR this might have
        // been a change synced from another editor

        if (this._visibleRange) {
            // We know all edits that happened in this editor must have been within the visible range. So
            // all we need to do is adjust the end of the visible range to account for the total number of
            // lines added/removed as the result of these edits.
            // Since we're certain these changes are generated by CodeMirror, not Document, we don't have
            // to check change.from/to for undefined.
            var numAdded = 0, change;
            for (change = changeList; change; change = change.next) {
                numAdded += change.text.length - (change.to.line - change.from.line + 1);
            }
            this._visibleRange.endLine += numAdded;
            //console.log("new visible range: " + this._visibleRange.startLine + " - " + this._visibleRange.endLine);
            // TODO: should double-check that the range of non-hidden lines after this matches up
            // with what we think _visibleRange is
        }
    };

    /**
     * Responds to changes in the Document's text, syncing the changes into our CodeMirror instance.
     * There are several cases where we want to ignore a Document change:
     *  - if we're the master editor, Document changes should be ignored becuase we already have the right
     *    text (either the change originated with us, or it has already been set into us by Document)
     *  - if we're a secondary editor, Document changes should be ignored if they were caused by us sending
     *    the document an editor change that originated with us
     */
    Editor.prototype._handleDocumentChange = function (event, doc, changeList) {
        var change;

        // we're currently syncing to the Document, so don't echo back FROM the Document
        if (this._duringSync) {
            return;
        }

        if (this.document._masterEditor !== this) {
            // Secondary editor:
            // we're not the ground truth; and if we got here, this was a Document change that
            // didn't come from us (e.g. a sync from another editor, a direct programmatic change
            // to the document, or a sync from external disk changes)... so sync from the Document

            // Special case: certain changes around the edges of the editor are problematic, because
            // if they're undone, we'll be unable to determine how to fix up the range to include the
            // undone content. (The "undo" will just look like an insertion outside our bounds.) So
            // in those cases, we close the inline editor instead of trying to fix it up. The specific
            // cases are:
            // 1. Edit crosses the start boundary of the inline editor (defined as character 0
            //    of the first line).
            // 2. Edit crosses the end boundary of the inline editor (defined as the newline at
            //    the end of the last line).
            // 3. Edit starts at the very beginning of the inline editor (defined as character 0
            //    of the first line) and crosses at least one newline.
            if (this._visibleRange) {
                var range = this._visibleRange, collapse;
                for (change = changeList; change; change = change.next) {
                    if (change.from === undefined || change.to === undefined) {
                        // Entire document was deleted, assume the editorneeds to collapse.
                        collapse = true;
                    } else {
                        collapse =
                            // Case 1
                            (change.from.line < range.startLine &&
                                change.to.line >= range.startLine) ||

                            // Case 2
                            (change.from.line <= range.endLine &&
                                change.to.line > range.endLine) ||

                            // Case 3
                            (change.from.line === range.startLine &&
                                change.from.ch === 0 &&
                                change.to.line > change.from.line);
                    }
                    if (collapse) {
                        $(this).triggerHandler("lostContent");
                        return;
                    }
                }
            }

            this._duringSync = true;
            this._applyChangesToEditor(this, changeList);
            this._duringSync = false;
        }
        // Else, Master editor:
        // we're the ground truth; nothing to do since Document change is just echoing our
        // editor changes
    };

    /**
     * Responds to the Document's underlying file being deleted. The Document is now basically dead,
     * so we must close.
     */
    Editor.prototype._handleDocumentDeleted = function () {
        $(this).triggerHandler("lostContent");
    };

    /**
     * Install singleton event handlers on the CodeMirror instance, translating them into multi-
     * listener-capable jQuery events on the Editor instance.
     */
    Editor.prototype._installEditorListeners = function () {
        var self = this;

        // FUTURE: if this list grows longer, consider making this a more generic mapping
        this._codeMirror.setOption("onChange", function (instance, changeList) {
            $(self).triggerHandler("change", [self, changeList]);
        });
        this._codeMirror.setOption("onKeyEvent", function (instance, event) {
            $(self).triggerHandler("keyEvent", [self, event]);
            return false;   // false tells CodeMirror we didn't eat the event
        });
        this._codeMirror.setOption("onCursorActivity", function (instance) {
            $(self).triggerHandler("cursorActivity", [self]);
        });
    };

    /**
     * @return {string} The editor's current contents
     * Semi-private: only Document/EditableDocumentModel should call this.
     */
    Editor.prototype._getText = function () {
        return this._codeMirror.getValue();
    };

    /**
     * Sets the contents of the editor. Treated as an edit: adds an undo step and dispatches a
     * change event.
     * Note: all line endings will be changed to LFs.
     * Semi-private: only Document/EditableDocumentModel should call this.
     * @param {!string} text
     */
    Editor.prototype._setText = function (text) {
        this._codeMirror.setValue(text);
    };

    /**
     * Sets the contents of the editor and clears the undo/redo history. Dispatches a change event.
     * Semi-private: only Document/EditableDocumentModel should call this.
     * @param {!string} text
     */
    Editor.prototype._resetText = function (text) {
        // This *will* fire a change event, but we clear the undo immediately afterward
        this._codeMirror.setValue(text);

        // Make sure we can't undo back to the empty state before setValue()
        this._codeMirror.clearHistory();
    };

    /**
     * Gets the current cursor position within the editor. If there is a selection, returns whichever
     * end of the range the cursor lies at.
     * @return !{line:number, ch:number}
     */
    Editor.prototype.getCursorPos = function () {
        return this._codeMirror.getCursor();
    };

    /**
     * Sets the cursor position within the editor. Removes any selection.
     * @param {number} line The 0 based line number.
     * @param {number} ch   The 0 based character position.
     */
    Editor.prototype.setCursorPos = function (line, ch) {
        this._codeMirror.setCursor(line, ch);
    };

    /**
     * Gets the current selection. Start is inclusive, end is exclusive. If there is no selection,
     * returns the current cursor position as both the start and end of the range (i.e. a selection
     * of length zero).
     * @return !{start:{line:number, ch:number}, end:{line:number, ch:number}}
     */
    Editor.prototype.getSelection = function () {
        var selStart = this._codeMirror.getCursor(true),
            selEnd = this._codeMirror.getCursor(false);
        return { start: selStart, end: selEnd };
    };

    /**
     * @return {!string} The currently selected text, or "" if no selection. Includes \n if the
     * selection spans multiple lines (does NOT reflect the Document's line-endings style).
     */
    Editor.prototype.getSelectedText = function () {
        return this._codeMirror.getSelection();
    };

    /**
     * Sets the current selection. Start is inclusive, end is exclusive. Places the cursor at the
     * end of the selection range.
     * @param {!{line:number, ch:number}} start
     * @param {!{line:number, ch:number}} end
     */
    Editor.prototype.setSelection = function (start, end) {
        this._codeMirror.setSelection(start, end);
    };

    /**
     * Gets the total number of lines in the the document (includes lines not visible in the viewport)
     * @returns {!number}
     */
    Editor.prototype.lineCount = function () {
        return this._codeMirror.lineCount();
    };

    /* Hides the specified line number in the editor
     * @param {!number}
     */
    Editor.prototype.hideLine = function (lineNumber) {
        return this._codeMirror.hideLine(lineNumber);
    };

    /**
     * Gets the total height of the document in pixels (not the viewport)
     * @param {!boolean} includePadding
     * @returns {!number} height in pixels
     */
    Editor.prototype.totalHeight = function (includePadding) {
        return this._codeMirror.totalHeight(includePadding);
    };

    /**
     * Gets the scroller element from the editor.
     * @returns {!HTMLDivElement} scroller
     */
    Editor.prototype.getScrollerElement = function () {
        return this._codeMirror.getScrollerElement();
    };

    /**
     * Adds an inline widget below the given line. If any inline widget was already open for that
     * line, it is closed without warning.
     * @param {!{line:number, ch:number}} pos  Position in text to anchor the inline.
     * @param {!DOMElement} domContent  DOM node of widget UI to insert.
     * @param {number} initialHeight  Initial height to accomodate.
     * @param {function()} parentShowCallback  Function called when the host editor is shown
     *          (via Editor.setVisible()).
     * @param {function()} closeCallback  Function called when inline is closed, either automatically
     *          by CodeMirror, or by this host Editor closing, or manually via removeInlineWidget().
     * @param {Object} data  Extra data to track along with the widget. Accessible later via
     *          {@link #getInlineWidgets()}.
     * @return {number} id for this inline widget instance; unique to this Editor
     */
    Editor.prototype.addInlineWidget = function (pos, domContent, initialHeight, parentShowCallback, closeCallback, data) {
        // Now add the new widget
        var self = this;
        var inlineId = this._codeMirror.addInlineWidget(pos, domContent, initialHeight, function (id) {
            self._removeInlineWidgetInternal(id);
            closeCallback();
        });
        this._inlineWidgets.push({ id: inlineId, data: data, parentShowCallback: parentShowCallback, closeCallback: closeCallback });

        return inlineId;
    };

    /**
     * Removes the given inline widget.
     * @param {number} inlineId  id returned by addInlineWidget().
     */
    Editor.prototype.removeInlineWidget = function (inlineId) {
        // _removeInlineWidgetInternal will get called from the destroy callback in CodeMirror.
        this._codeMirror.removeInlineWidget(inlineId);
    };

    /**
     * Cleans up the given inline widget from our internal list of widgets.
     * @param {number} inlineId  id returned by addInlineWidget().
     */
    Editor.prototype._removeInlineWidgetInternal = function (inlineId) {
        var i;
        for (i = 0; i < this._inlineWidgets.length; i++) {
            if (this._inlineWidgets[i].id === inlineId) {
                this._inlineWidgets.splice(i, 1);
                break;
            }
        }
    };

    /**
     * Returns a list of all inline widgets currently open in this editor. Each entry contains the
     * inline's id, and the data parameter that was passed to addInlineWidget().
     * @return {!Array.<{id:number, data:Object}>}
     */
    Editor.prototype.getInlineWidgets = function () {
        return this._inlineWidgets;
    };

    /**
     * Sets the height of the inline widget for this editor. The inline editor is identified by id.
     * @param {!number} id
     * @param {!height} height
     * @param {boolean} ensureVisible
     */
    Editor.prototype.setInlineWidgetHeight = function (id, height, ensureVisible) {
        this._codeMirror.setInlineWidgetHeight(id, height, ensureVisible);
    };

    /** Gives focus to the editor control */
    Editor.prototype.focus = function () {
        this._codeMirror.focus();
    };

    /** Returns true if the editor has focus */
    Editor.prototype.hasFocus = function () {
        // The CodeMirror instance wrapper has a "CodeMirror-focused" class set when focused
        return $(this._codeMirror.getWrapperElement()).hasClass("CodeMirror-focused");
    };

    /**
     * Re-renders the editor UI
     */
    Editor.prototype.refresh = function () {
        this._codeMirror.refresh();
    };

    /**
     * Shows or hides the editor within its parent. Does not force its ancestors to
     * become visible.
     * @param {boolean} show true to show the editor, false to hide it
     */
    Editor.prototype.setVisible = function (show) {
        $(this._codeMirror.getWrapperElement()).css("display", (show ? "" : "none"));
        this._codeMirror.refresh();
        if (show) {
            this._inlineWidgets.forEach(function (widget) {
                if (widget.parentShowCallback) {
                    widget.parentShowCallback();
                }
            });
        }
    };

    /**
     * Returns true if the editor is fully visible--i.e., is in the DOM, all ancestors are
     * visible, and has a non-zero width/height.
     */
    Editor.prototype.isFullyVisible = function () {
        return $(this._codeMirror.getWrapperElement()).is(":visible");
    };

    /**
     * The Document we're bound to
     * @type {!Document}
     */
    Editor.prototype.document = null;

    /**
     * If true, we're in the middle of syncing to/from the Document. Used to ignore spurious change
     * events caused by us (vs. change events caused by others, which we need to pay attention to).
     * @type {!boolean}
     */
    Editor.prototype._duringSync = false;

    /**
     * @private
     * NOTE: this is actually "semi-private": EditorManager also accesses this field... as well as
     * a few other modules. However, we should try to gradually move most code away from talking to
     * CodeMirror directly.
     * @type {!CodeMirror}
     */
    Editor.prototype._codeMirror = null;

    /**
     * @private
     * @type {!Array.<{id:number, data:Object}>}
     */
    Editor.prototype._inlineWidgets = null;

    /**
     * @private
     * @type {{startLine: number, endLine: number}}
     */
    Editor.prototype._visibleRange = null;

    // Define public API
    exports.Editor = Editor;
});


html {
    color: "red";
}

HTML {
    color: "orange";
}

.firstGrade {
    color: "green";
}

.FIRSTGRADE {
    color: "yellow";
}

#brack3ts {
    color: "blue";
}

#BRACK3TS {
    color: "black";
}


keep keep keep keep replace keep keep keep keep

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

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

/* Overall layout */

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

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

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

body {
    .vbox;

    /* This appears to be necessary in Firefox when body is set to display: box. */
    width: 100%;

    .topbar {
        position: static;
    }
}

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

.topbar-inner {
    /* We don't want to use the default .container in here,
       so we need to .clearfix the children. */
    .clearfix;

    .brand {
        .sane-box-model;
        margin-left: 0;
        width: 200px;
        padding-left: @base-padding;
        padding-right: @base-padding;
    }
}

.main-view {
    .hbox;
    .box-flex(1);

    .sidebar {
        .vbox;
        width: 200px;
    }

    .content {
        .vbox;
        .box-flex(1);
    }
}

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

#editorHolder {
    .vbox;
    .box-flex(1);

    /* Placeholder shown when there is no editor open */
    #notEditor {
        .box-flex(1);
        .vbox;
        .box-pack(center);
        .box-align(center);
        background-color: @background-color-2;

        #notEditorContent {
            color: @background-color-1;
            font-size: 350px;
            line-height: normal;
            margin-top: -0.25em;
            text-shadow: 0 1px @bc-black;
            opacity: 0.30;
        }
    }
}

#jslint-results, #search-results {
    display: none;
    height: 200px;
    border-top-style: solid;
    border-width: 1px;
    border-color: lighten(@bc-grey, @bc-color-step-size*4);

    .toolbar {
        height: auto;
        padding-top: @base-padding / 2;
        padding-bottom: @base-padding / 2;
        font-size: 0.9em;
        .box-shadow(0 -1px 3px 0 fadeout(@bc-black, 70%));

        .close {
            position: absolute;
            right: 10px;
        }
    }

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

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

        .highlight {
            background: #d2dcf8;
        }

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

#gold-star {
    display: none;
    font-size: 1.2em;
    color: lighten(@bc-yellow, @bc-color-step-size*2);
    text-shadow: 0 -1px @bc-black;
}

/* Project panel */

#projects .toolbar {
    .picker {
        .box;
        .box-flex(1);
    }
}

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

    .project-file-header-area {
        padding: 4px 4px 2px 0px;

        span {
            font-weight: bold;
            vertical-align: middle;
        }
    }

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

}

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

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

        a {
            color: black;
            text-decoration: none;
            margin-left: 26px;
        }

        li {
            padding: 2px 0px 2px 2px;
            white-space: nowrap;
            position: relative; // so that children can be positioned absolute

            &:hover {
                background: #E7F4F9;
            }

            &.selected {
                #gradient.vertical(#5fa3e0, #0065be);

                a { color: white; }
            }
        }
    }
}

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

#project-files-container {
    .box-flex(1);
    padding: 0px 0px 0px 8px;
    margin: 0;
    overflow: auto;

    ul { margin-top: 0; }
}

@spriteSize18: 18px;

/** Classes for icons from jsTreeSprites.png
*/
.jsTreeSprite {
    background-image: url("styles/images/jsTreeSprites.png");
    background-repeat: no-repeat;
    background-color: transparent;
    vertical-align: middle;
    width: @spriteSize18;
    height: @spriteSize18;
}

.disclosure-arrow-opened {
    .jsTreeSprite;
    display: inline-block;
    background-position: -@spriteSize18 0px;
}

.disclosure-arrow-closed {
    .jsTreeSprite;
    display: inline-block;
    background-position: 0px 0px;
}

/** Classes for icons from bracketSprites.png
*/

.bracketSprite {
    background-image: url("styles/images/bracketsSprites.png");
    background-repeat: no-repeat;
    background-color: transparent;
    vertical-align: middle;
    width: @spriteSize18;
    height: @spriteSize18;
}

.file-status-icon {
    margin: 0px 0px 0px 10px;
    .bracketSprite;
    display: inline-block;
    position: absolute;
    left: 0px;
    top: 1px;

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

    &.canClose {
        background-position: -@spriteSize18 0;
    }

    &.canClose:hover {
        background-position: -@spriteSize18*2 0;
    }
}

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

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

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

/* Styles for inline editors */
.InlineWidget {
    border-top:         1px solid #C0C0C0;
    border-bottom:      1px solid #C0C0C0;
    min-width:          100%;
    background-color:   #eaeaea;

    .filename {
        font-family: sans-serif;
        font-size: 14px;
        color: #656565;
        padding: 10px 10px 0px 10px;

        .dirty-indicator {
            .bracketSprite;
            display: inline-block;
            background-position: 0 0;
            padding-top: 3px;
        }
    }
    .shadow {
        display: block;
        height: 6px;
        width: 100%;
        position: absolute;
        content: " ";
        left: 0;
        z-index: 10;
    }
    .shadow.top {
        top: 0px;
        background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0));
    }
    .shadow.bottom {
        bottom: -1px;
        background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1));
    }

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

.relatedContainer {
    /*width: 0;*/
    font-family: sans-serif;
    position: fixed;
    right: 0;
    width: 250px;
    overflow: auto;
    /*-webkit-transition: width 0.15s ease-out;*/
    background: #f2f2f2;
    border-left: 1px solid #ccc;

    @top-margin: 12px;

    .selection {
        display: block;
        width: 100%;
        background: #e6e6e6;
        position: absolute;
        -webkit-transition: height 0.1s ease-out;
        -webkit-transition: top 0.1s ease-out;
        border-top: 1px solid #d6d6d6;
        border-bottom: 1px solid #d6d6d6;
        box-shadow: 0 1px 0 #fff;
        top: @top-margin;
    }

    .related {
        position: absolute;
        top: 0;
        left: 1px;
        height: 100%;
        box-shadow: -1px 0 0 0 #fff;
        font-size: 12px;

        ul {
            margin: 0;
            padding: @top-margin 0px;
            list-style: none;
            color: #656565;

            li {
                padding: 0px 15px;
                white-space: nowrap;
            }

            .location {
                color: #acacac;
            }
        }
    }
}

/* Styles for the search dialog--this is temporary, only for debugging */

.CodeMirror-dialog {
  position: relative;
  z-index: 11;
}

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

.CodeMirror-dialog input {
  font-family: sans-serif;
  border: 1px solid @content-color-weaker;
  outline: none;
  background: @background-color-3;
  width: 20em;
  margin: .3em .3em;
  color: inherit;
}

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

.smart_autocomplete_container {

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

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

    .quickOpenPath {
        font-size: 11px;
        color: gray;
    }

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

li.smart_autocomplete_highlight {
    background-color: #5fa3e0;

}