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

/* Styles for jsTree control */
/* (these are based on jsTree's default theme .css file, so they are not very LESS-like) */

.jstree-brackets li,
.jstree-brackets ins { background-image:url("styles/images/jsTreeSprites.png"); background-repeat:no-repeat; background-color:transparent; }
.jstree-brackets li { background-position:-90px 0; background-repeat:repeat-y; }
.jstree-brackets li.jstree-last { background:transparent; }
.jstree-brackets .jstree-open > ins { background-position:-72px 0; }
.jstree-brackets .jstree-closed > ins { background-position:-54px 0; }
.jstree-brackets .jstree-leaf > ins { background-position:-36px 0; }

/* Make the links in the JS tree the width of the container
 * by shifting the off the screen by negative margin and moving the
 * content back by pushing the padding. The icons are positioned absolute
 * relative to the containing list item so the sit above the a's background.
 * This also means we need to include the size of the sprite in the padding
 * so the text ends up back in the same spot visually
 */
.jstree-brackets li > a {
    padding-left: @sprite-size-18 + 10000;
    margin-left: -10000px;
    display: block;
}

.jstree li {
    position: relative;
}

.jstree-brackets li {
    line-height: 10px;
    vertical-align: baseline;
    a {
        color: #BBB;
        font-size: 13px;
        cursor: default;

        &.jstree-clicked {
            color: #fff;
        }
    }
    .extension a {
        color: #8e9a9d;
    }
    &.jstree-closed, &.jstree-open {
        margin-left: 10px;
        > a {
            color: #fff;
        }
    }
    &.jstree-leaf {
        margin-left: 10px;
    }
}

.jstree ins {
    position: absolute;
}

.jstree-brackets a .jstree-icon { background-position:-56px -19px; }
.jstree-brackets a.jstree-loading .jstree-icon { background:url("styles/images/throbber.gif") center center no-repeat !important; }

.jstree-brackets li > a {
    padding-top: 3px;
    padding-bottom: 3px;
}
.jstree-brackets li > ins {
    margin-top: 2px;
    margin-bottom: 2px;
}

.jstree-brackets .jstree-no-dots li,
.jstree-brackets .jstree-no-dots .jstree-leaf > ins { background:transparent; }
.jstree-brackets .jstree-no-dots .jstree-open > ins { background-position:-18px 0; }
.jstree-brackets .jstree-no-dots .jstree-closed > ins { background-position:0 0; }

.jstree-brackets .jstree-no-icons a .jstree-icon { display:none; }

.jstree-brackets .jstree-search { font-style:italic; }

.jstree-brackets .jstree-no-icons .jstree-checkbox { display:inline-block; }
.jstree-brackets .jstree-no-checkboxes .jstree-checkbox { display:none !important; }
.jstree-brackets .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; }
.jstree-brackets .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; }
.jstree-brackets .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; }
.jstree-brackets .jstree-checked > a > .jstree-checkbox:hover { background-position:-38px -37px; }
.jstree-brackets .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; }
.jstree-brackets .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; }

#vakata-dragged.jstree-brackets ins { background:transparent !important; }
#vakata-dragged.jstree-brackets .jstree-ok { background:url("styles/images/jsTreeSprites.png") -2px -53px no-repeat !important; }
#vakata-dragged.jstree-brackets .jstree-invalid { background:url("styles/images/jsTreeSprites.png") -18px -53px no-repeat !important; }
#jstree-marker.jstree-brackets { background:url("styles/images/jsTreeSprites.png") -41px -57px no-repeat !important; text-indent:-100px; }

.jstree-brackets a.jstree-search { color:aqua; }
.jstree-brackets .jstree-locked a { color:silver; cursor:default; }

.jstree-brackets .jstree-rename-input { color: black; }

#vakata-contextmenu.jstree-brackets-context,
#vakata-contextmenu.jstree-brackets-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; }
#vakata-contextmenu.jstree-brackets-context li { }
#vakata-contextmenu.jstree-brackets-context a { color:black; }
#vakata-contextmenu.jstree-brackets-context a:hover,
#vakata-contextmenu.jstree-brackets-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; }
#vakata-contextmenu.jstree-brackets-context li.jstree-contextmenu-disabled a,
#vakata-contextmenu.jstree-brackets-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; }
#vakata-contextmenu.jstree-brackets-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; }
#vakata-contextmenu.jstree-brackets-context li ul { margin-left:-4px; }


case "java":
return "clike";
case "coffee":
return "coffeescript";

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

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global $: false, 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 from brackets.test
    var CommandManager,
        Commands,
        DocumentManager,
        FileViewController,
        SpecRunnerUtils     = require("./SpecRunnerUtils.js");

    describe("WorkingSetView", function () {

        var testPath = SpecRunnerUtils.getTestPath("/spec/WorkingSetView-test-files");
        var testWindow;

        beforeEach(function () {
            SpecRunnerUtils.createTestWindowAndRun(this, function (w) {
                testWindow = w;

                // Load module instances from brackets.test
                CommandManager      = testWindow.brackets.test.CommandManager;
                Commands            = testWindow.brackets.test.Commands;
                DocumentManager     = testWindow.brackets.test.DocumentManager;
                FileViewController  = testWindow.brackets.test.FileViewController;

                // Open a directory
                SpecRunnerUtils.loadProjectInTestWindow(testPath);
            });

            var workingSetCount = 0;

            runs(function () {
                // Initialize: register listeners
                testWindow.$(DocumentManager).on("workingSetAdd", function (event, addedFile) {
                    workingSetCount++;
                });
            });

            var openAndMakeDirty = function (path) {
                var doc, didOpen = false, gotError = false;

                // open file
                runs(function () {
                    FileViewController.openAndSelectDocument(path, FileViewController.PROJECT_MANAGER)
                        .done(function () { didOpen = true; })
                        .fail(function () { gotError = true; });
                });
                waitsFor(function () { return didOpen && !gotError; }, "FILE_OPEN on file timeout", 1000);

                // change editor content to make doc dirty which adds it to the working set
                runs(function () {
                    doc = DocumentManager.getCurrentDocument();
                    doc.setText("dirty document");
                });
            };

            openAndMakeDirty(testPath + "/file_one.js");
            openAndMakeDirty(testPath + "/file_two.js");

            // Wait for both files to be added to the working set
            waitsFor(function () { return workingSetCount === 2; }, 1000);
        });

        afterEach(function () {
            SpecRunnerUtils.closeTestWindow();
        });

        it("should add a list item when a file is dirtied", function () {
            // check if files are added to work set and dirty icons are present
            runs(function () {
                var $listItems = testWindow.$("#open-files-container > ul").children();
                expect($listItems.length).toBe(2);
                expect($listItems.find("a").get(0).text === "file_one.js").toBeTruthy();
                expect($listItems.find(".file-status-icon").length).toBe(2);
            });
        });

        it("should remove a list item when a file is closed", function () {
            DocumentManager.getCurrentDocument()._markClean(); // so we can close without a save dialog

            // close the document
            var didClose = false, gotError = false;
            runs(function () {
                CommandManager.execute(Commands.FILE_CLOSE)
                    .done(function () { didClose = true; })
                    .fail(function () { gotError = true; });
            });
            waitsFor(function () { return didClose && !gotError; }, "FILE_OPEN on file timeout", 1000);

            // check there are no list items
            runs(function () {
                var listItems = testWindow.$("#open-files-container > ul").children();
                expect(listItems.length).toBe(1);
            });
        });

        it("should make a file that is clicked the current one in the editor", function () {
            runs(function () {
                var $ = testWindow.$;
                var secondItem =  $($("#open-files-container > ul").children()[1]);
                secondItem.trigger('click');

                var $listItems = $("#open-files-container > ul").children();
                expect($($listItems[0]).hasClass("selected")).not.toBeTruthy();
                expect($($listItems[1]).hasClass("selected")).toBeTruthy();
            });
        });

        it("should rebuild the ui from the model correctly", function () {
            // force the test window to initialize to unit test preferences
            // for just this test
            runs(function () {
                localStorage.setItem("doLoadPreferences", true);
            });

            // remove temporary unit test preferences with a single-spec after()
            this.after(function () {
                localStorage.removeItem("doLoadPreferences");
            });

            // close test window while working set has 2 files (see beforeEach())
            SpecRunnerUtils.closeTestWindow();

            // reopen brackets test window to initialize unit test working set
            SpecRunnerUtils.createTestWindowAndRun(this, function (w) {
                testWindow = w;
            });

            var $listItems;

            // wait for working set to populate
            waitsFor(
                function () {
                    // check working set UI list content
                    $listItems = testWindow.$("#open-files-container > ul").children();
                    return $listItems.length > 0;
                },
                1000
            );

            // files should be in the working set
            runs(function () {
                expect($listItems.find("a").get(0).text === "file_one.js").toBeTruthy();
                expect($listItems.find("a").get(1).text === "file_two.js").toBeTruthy();

                // files should be clean
                expect($listItems.find(".file-status-icon dirty").length).toBe(0);

                // file_two.js should be active
                expect($($listItems[1]).hasClass("selected")).toBeTruthy();
            });
        });

        it("should close a file when the user clicks the close button", function () {
            var $ = testWindow.$;
            var didClose = false;

            // make 2nd doc clean
            var fileList = DocumentManager.getWorkingSet();

            runs(function () {
                var doc0 = DocumentManager.getOpenDocumentForPath(fileList[0].fullPath);
                var doc1 = DocumentManager.getOpenDocumentForPath(fileList[1].fullPath);
                doc1._markClean();

                // make the first one active
                DocumentManager.setCurrentDocument(doc0);

                // hover over and click on close icon of 2nd list item
                var secondItem =  $($("#open-files-container > ul").children()[1]);
                secondItem.trigger('mouseover');
                var closeIcon = secondItem.find(".file-status-icon");
                expect(closeIcon.length).toBe(1);

                // simulate click
                $(DocumentManager).on("workingSetRemove", function (event, removedFile) {
                    didClose = true;
                });

                closeIcon.trigger('click');
            });

            waitsFor(function () { return didClose; }, "click on working set close icon timeout", 1000);

            runs(function () {
                var $listItems = $("#open-files-container > ul").children();
                expect($listItems.length).toBe(1);
                expect($listItems.find("a").get(0).text === "file_one.js").toBeTruthy();
            });
        });

        it("should remove dirty icon when file becomes clean", function () {
            runs(function () {
                // check that dirty icon is removed when docs are cleaned
                var fileList = DocumentManager.getWorkingSet();
                var doc0 = DocumentManager.getOpenDocumentForPath(fileList[0].fullPath);
                doc0._markClean();

                var listItems = testWindow.$("#open-files-container > ul").children();
                expect(listItems.find(".file-status-icon dirty").length).toBe(0);
            });
        });

    });
});


keep keep keep replace keep keep keep keep

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

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

/**
 * 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");
    require("thirdparty/CodeMirror2/mode/xml/xml");
    require("thirdparty/CodeMirror2/mode/clike/clike");
    require("thirdparty/CodeMirror2/mode/php/php");
    require("thirdparty/CodeMirror2/mode/coffeescript/coffeescript");
    require("thirdparty/CodeMirror2/mode/clojure/clojure");
    require("thirdparty/CodeMirror2/mode/perl/perl");
    require("thirdparty/CodeMirror2/mode/ruby/ruby");
    require("thirdparty/CodeMirror2/mode/mysql/mysql");
    require("thirdparty/CodeMirror2/mode/diff/diff");
    require("thirdparty/CodeMirror2/mode/markdown/markdown");

    /**
     * @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":
        case "xhtml":
            return "htmlmixed";

        case "xml":
            return "xml";

        case "php":
            return "php";

        case "c":
        case "cc":
        case "c++":
        case "cxx":
        case "cp":
        case "cpp":
        case "h":
        case "hpp":
        case "vert":
        case "frag":
        case "java":
            return "clike";

        case "coffee":
            return "coffeescript";

        case "clj":
            return "clojure";

        case "pl":
            return "perl";

        case "rb":
            return "ruby";

        case "sql":
            return "mysql";

        case "diff":
        case "patch":
            return "diff";

        case "md":
            return "markdown";

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

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


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

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

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

    // Load dependent modules
    var HTMLUtils     = require("language/HTMLUtils"),
        EditorManager = require("editor/EditorManager");

    /**
     * @private
     * Test functions to see if the hinting is working
     * @param {CodeMirror} editor An instance of a CodeMirror editor
     */
    function _triggerClassHint(editor, pos, tagInfo) {
        //console.log("_triggerClassHint called for tag: " + tagInfo.tagName + " and attr value: " + tagInfo.attr.value);
    }

    function _triggerIdHint(editor, pos, tagInfo) {
        //console.log("_triggerIdHint called for tag: " + tagInfo.tagName + " and attr value: " + tagInfo.attr.value);
    }

    /**
     * @private
     * Checks to see if this is an attribute value we can hint
     * @param {CodeMirror} editor An instance of a CodeMirror editor
     */
    function _checkForHint(editor) {
        var pos = editor.getCursor();
        var tagInfo = HTMLUtils.getTagInfo(editor, pos);
        if (tagInfo.position.type === HTMLUtils.ATTR_VALUE) {
            if (tagInfo.attr.name === "class") {
                _triggerClassHint(editor, pos, tagInfo);
            } else if (tagInfo.attr.name === "id") {
                _triggerIdHint(editor, pos, tagInfo);
            }
        }
    }

    /**
     * @private
     * Called whenever a CodeMirror editor gets a key event
     * @param {object} event the jQuery event for onKeyEvent
     * @param {CodeMirror} editor An instance of a CodeMirror editor
     * @param {object} keyboardEvent  the raw keyboard event that CM is handling
     */
    function _onKeyEvent(event, editor, keyboardEvent) {
        if (keyboardEvent.type !== "keypress") {
            return;
        }
        window.setTimeout(function () { _checkForHint(editor); }, 40);
    }

    // Register our listeners
    // Commenting out the code hinting for now. Uncomment this line to re-enable.
    // NOTE: this has gone stale a bit; individual Editors now dispatch a keyEvent event; there is
    // no global EditorManager event
    //$(EditorManager).on("onKeyEvent", _onKeyEvent);

    // Define public API
});


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

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

/**
 * Set of utilities for simple parsing of JS text.
 */
define(function (require, exports, module) {
    'use strict';

    // Load brackets modules
    var Async               = brackets.getModule("utils/Async"),
        DocumentManager     = brackets.getModule("document/DocumentManager"),
        FileIndexManager    = brackets.getModule("project/FileIndexManager"),
        NativeFileSystem    = brackets.getModule("file/NativeFileSystem").NativeFileSystem;

    // Return an Array with names and offsets for all functions in the specified text
    function _findAllFunctionsInText(text) {
        var result = [];
        var regexA = new RegExp(/(function\b)(.+)\b\(.*?\)/gi);  // recognizes the form: function functionName()
        var regexB = new RegExp(/(\w+)\s*=\s*function\s*(\(.*?\))/gi); // recognizes the form: functionName = function()
        var regexC = new RegExp(/((\w+)\s*:\s*function\s*\(.*?\))/gi); // recognizes the form: functionName: function()
        var match;

        while ((match = regexA.exec(text)) !== null) {
            result.push({
                functionName: match[2].trim(),
                offset: match.index
            });
        }

        while ((match = regexB.exec(text)) !== null) {
            result.push({
                functionName: match[1].trim(),
                offset: match.index
            });
        }

        while ((match = regexC.exec(text)) !== null) {
            result.push({
                functionName: match[2].trim(),
                offset: match.index
            });
        }

        return result;
    }

    // Simple scanner to determine the end offset for the function (the closing "}")
    function _getFunctionEndOffset(text, offsetStart) {
        var curOffset = text.indexOf("{", offsetStart) + 1;
        var length = text.length;
        var blockCount = 1;

        // Brute force scan to look for closing curly match
        // NOTE: This does *not* handle comments.
        while (curOffset < length) {
            if (text[curOffset] === "{") {
                blockCount++;
            } else if (text[curOffset] === "}") {
                blockCount--;
            }

            curOffset++;

            if (blockCount <= 0) {
                return curOffset;
            }
        }

        // Shouldn't get here, but if we do, return the end of the text as the offset
        return length;
    }

    function _offsetToLineNum(text, offset) {
        return text.substr(0, offset).split("\n").length - 1;
    }

    // Search function list for a specific function. If found, extract info about the function
    // (line start, line end, etc.) and return.
    function _findAllMatchingFunctions(fileInfo, functions, functionName) {
        var result = new $.Deferred();
        var foundMatch = false;

        functions.forEach(function (funcEntry) {
            if (funcEntry.functionName === functionName) {
                var matchingFunctions = [];

                DocumentManager.getDocumentForPath(fileInfo.fullPath)
                    .done(function (doc) {
                        var text = doc.getText();

                        functions.forEach(function (funcEntry) {
                            if (funcEntry.functionName === functionName) {
                                var endOffset = _getFunctionEndOffset(text, funcEntry.offset);
                                matchingFunctions.push({
                                    document: doc,
                                    name: funcEntry.functionName,
                                    lineStart: _offsetToLineNum(text, funcEntry.offset),
                                    lineEnd: _offsetToLineNum(text, endOffset)
                                });
                            }
                        });

                        result.resolve(matchingFunctions);
                    })
                    .fail(function (error) {
                        result.reject(error);
                    });
            }
        });

        if (!foundMatch) {
            result.resolve([]);
        }

        return result.promise();
    }

    // Read a file and build a function list. Result is cached in fileInfo.
    function _readFileAndGetFunctionList(fileInfo, result) {

        DocumentManager.getDocumentForPath(fileInfo.fullPath)
            .done(function (doc) {
                var allFunctions = _findAllFunctionsInText(doc.getText());

                // Cache the result in the fileInfo object
                fileInfo.JSUtils = {};
                fileInfo.JSUtils.functions = allFunctions;
                fileInfo.JSUtils.timestamp = doc.diskTimestamp;

                result.resolve(allFunctions);
            })
            .fail(function (error) {
                result.reject(error);
            });
    }

    // Resolves with an Array of all function names and offsets for the specified file. Results
    // may be cached.
    function _getFunctionListForFile(fileInfo) {
        var result = new $.Deferred();
        var needToRead = false;

        // Always read if we have no cached data
        if (!fileInfo.JSUtils) {
            needToRead = true;
        }

        // If the document is dirty, we need to read it
        if (!needToRead) {
            var openDocs = DocumentManager.getAllOpenDocuments();
            openDocs.forEach(function (doc) {
                if (doc.file.fullPath === fileInfo.fullPath && doc.isDirty) {
                    needToRead = true;
                }
            });
        }

        // Finally, see if we have missing or stale cache data
        if (!needToRead) {
            var file = new NativeFileSystem.FileEntry(fileInfo.fullPath);

            file.getMetadata(
                function (metadata) {
                    if (fileInfo.JSUtils.timestamp !== metadata.diskTimestamp) {
                        _readFileAndGetFunctionList(fileInfo, result);
                    } else {
                        // Return cached data
                        result.resolve(fileInfo.JSUtils.functions);
                    }
                },
                function (error) {
                    result.reject(error);
                }
            );
        } else {
            _readFileAndGetFunctionList(fileInfo, result);
        }

        return result.promise();
    }

    // Search for all matching functions in a file
    function _getMatchingFunctionsInFile(fileInfo, functionName, resultFunctions) {
        var oneFileResult = new $.Deferred();
        var fullPath = fileInfo.fullPath;

        // Only scan JS files
        var ext = PathUtils.filenameExtension(fullPath);

        if (!/^\.js/i.test(ext)) {
            oneFileResult.resolve();
            return oneFileResult.promise();
        }

        _getFunctionListForFile(fileInfo)
            .done(function (functionList) {
                _findAllMatchingFunctions(fileInfo, functionList, functionName)
                    .done(function (matches) {
                        matches.forEach(function (functionInfo) {
                            resultFunctions.push(functionInfo);
                        });
                        oneFileResult.resolve();
                    })
                    .fail(function (error) {
                        oneFileResult.reject(error);
                    });
            })
            .fail(function (error) {
                oneFileResult.reject(error);
            });

        return oneFileResult.promise();
    }

    /**
     * Return all functions that have the specified name.
     *
     * @param {!String} functionName The name to match.
     * @return {$.Promise} that will be resolved with an Array of objects containing the
     *      source document, start line, and end line (0-based, inclusive range) for each matching function list.
     *      Does not addRef() the documents returned in the array.
     */
    function findMatchingFunctions(functionName) {
        var result          = new $.Deferred(),
            resultFunctions = [],
            jsFilesResult   = FileIndexManager.getFileInfoList("all");

        // Load index of all CSS files; then process each CSS file in turn (see above)
        jsFilesResult.done(function (fileInfos) {
            Async.doInParallel(fileInfos, function (fileInfo, number) {
                return _getMatchingFunctionsInFile(fileInfo, functionName, resultFunctions);
            })
                .done(function () {
                    result.resolve(resultFunctions);
                })
                .fail(function (error) {
                    result.reject(error);
                });
        });

        return result.promise();
    }

    exports.findMatchingFunctions = findMatchingFunctions;
});


span.CodeMirror-matchingbracket {color: @accent-bracket !important; background-color: @background-color-2;}

span.CodeMirror-nonmatchingbracket {color: @accent-bracket !important;}

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

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

/* Overall Colors */

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

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

/* Code Styling */

/* code accent colors */
@accent-keyword: #446fbd;
@accent-atom: #f18900;
@accent-number: #738d00;
@accent-def: #8757ad;
@accent-variable: #446fbd;
@accent-variable-2: #535353;
@accent-variable-3: #535353;
@accent-property: #8757ad;
@accent-operator: #535353;
@accent-comment: #a2a2a2;
@accent-string: #f18900;
@accent-string-2: #f18900;
@accent-meta: #535353;
@accent-error: #dc322f;
@accent-qualifier: #446fbd;
@accent-builtin: #446fbd;
@accent-bracket: #535353;
@accent-tag: #446fbd;
@accent-attribute: #738d00;
@accent-header: #d33682;
@accent-quote: #446fbd;
@accent-hr: #f18900;
@accent-link: #8757ad;

/* inline editor colors */
@inline-background-color-1: #eaeaea;
@inline-background-color-2: #dfe2e2;
@inline-background-color-3: #d0d5d5;

@inline-color-1: #a1a3a4;
@inline-color-2: #8a8a8a;
@inline-color-3: @bc-black;

/* Code font formatting
 *
 * NOTE: The fonts below must be system fonts (native installed fonts), not web fonts. To use a web font we'd need to
 * jump through some additional hoops (and we couldn't easily vary the font by OS like we do now). See earlier versions
 * of this file for details.
 */
.code-font() {
    color: @content-color;
    pre {
        // line-height must be specified in px not em because the code font and line number font sizes are different.
        // Sizing via em will cause the code and line numbers to misalign
        line-height: 15px;
    }
}

.code-font-win() {
    .code-font();
    font-family: Consolas, "Lucida Console", "Courier New", monospace;
    font-size: 13px;    /* TODO: ideally, we want 12px for Lucida & Courier */
}

.code-font-mac() {
    .code-font();
    font-family: Menlo, Monaco, monospace;
    font-size: 12px;
}

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

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

}

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


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

!function( $ ){

  "use strict"

  function activate ( element, container ) {
    container
      .find('> .active')
      .removeClass('active')
      .find('> .dropdown-menu > .active')
      .removeClass('active')

    element.addClass('active')

    if ( element.parent('.dropdown-menu') ) {
      element.closest('li.dropdown').addClass('active')
    }
  }

  function tab( e ) {
    var $this = $(this)
      , $ul = $this.closest('ul:not(.dropdown-menu)')
      , href = $this.attr('href')
      , previous
      , $href

    if ( /^#\w+/.test(href) ) {
      e.preventDefault()

      if ( $this.parent('li').hasClass('active') ) {
        return
      }

      previous = $ul.find('.active a').last()[0]
      $href = $(href)

      activate($this.parent('li'), $ul)
      activate($href, $href.parent())

      $this.trigger({
        type: 'change'
      , relatedTarget: previous
      })
    }
  }

 /* TABS/PILLS PLUGIN DEFINITION
  * ============================ */

  $.fn.tabs = $.fn.pills = function ( selector ) {
    return this.each(function () {
      $(this).delegate(selector || '.tabs li > a, .pills > li > a', 'click', tab)
    })
  }

  $(document).ready(function () {
    $('body').tabs('ul[data-tabs] li > a, ul[data-pills] > li > a')
  })

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


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

/* Brackets Variables */

/* All paddings, gutters, etc. should be multiples of this */
@base-padding: 10px;

/* Common font sizes */
@title-font-size: 18px;  // headings such as the editor titlebar
@label-font-size: 14px;  // labels on buttons, menubar, etc.
@menu-item-font-size: 13px; // individual menu items

/* CSS triangle */
@triangle-size: 9px;

/* sidebar */
@sidebar-width: 200px;

/* z-index */
@z-index-cm-dialog-override: 11;
@z-index-cm-dialog: 15;
@z-index-cm-max: @z-index-cm-dialog;

@z-index-brackets-ui: @z-index-cm-max + 1;
@z-index-brackets-selection-triangle: @z-index-brackets-ui;
@z-index-brackets-results-panel: @z-index-brackets-ui;
@z-index-brackets-scroller-shadow: @z-index-brackets-selection-triangle + 1;
@z-index-brackets-inline-editor-shadow: @z-index-brackets-ui;
@z-index-brackets-toolbar: @z-index-brackets-scroller-shadow + 1;
@z-index-brackets-max: @z-index-brackets-toolbar;

@z-index-brackets-sidebar-resizer: @z-index-brackets-ui + 2;


/* Forms.less
 * Base styles for various input types, form layouts, and states
 * ------------------------------------------------------------- */

// FORM STYLES
// -----------

form {
  margin-bottom: @baseline;
}

// Groups of fields with labels on top (legends)
fieldset {
  margin-bottom: @baseline;
  padding-top: @baseline;
  legend {
    display: block;
    padding-left: 150px;
    font-size: @basefont * 1.5;
    line-height: 1;
    color: @grayDark;
    *padding: 0 0 5px 145px; /* IE6-7 */
    *line-height: 1.5; /* IE6-7 */
  }
}

// Parent element that clears floats and wraps labels and fields together
form .clearfix {
  margin-bottom: @baseline;
  .clearfix()
}

// Set font for forms
label,
input,
select,
textarea {
  #font > .sans-serif(normal,13px,normal);
}

// Float labels left
label {
  padding-top: 6px;
  font-size: @basefont;
  line-height: @baseline;
  float: left;
  width: 130px;
  text-align: right;
  color: @grayDark;
}

// Shift over the inside div to align all label's relevant content
form .input {
  margin-left: 150px;
}

// Checkboxs and radio buttons
input[type=checkbox],
input[type=radio] {
  cursor: pointer;
}

// Inputs, Textareas, Selects
input,
textarea,
select,
.uneditable-input {
  display: inline-block;
  width: 210px;
  height: @baseline;
  padding: 4px;
  font-size: @basefont;
  line-height: @baseline;
  color: @gray;
  border: 1px solid #ccc;
  .border-radius(3px);
}

// remove padding from select
select {
  padding: initial;
}

// mini reset for non-html5 file types
input[type=checkbox],
input[type=radio] {
  width: auto;
  height: auto;
  padding: 0;
  margin: 3px 0;
  *margin-top: 0; /* IE6-7 */
  line-height: normal;
  border: none;
}

input[type=file] {
  background-color: @white;
  padding: initial;
  border: initial;
  line-height: initial;
  .box-shadow(none);
}

input[type=button],
input[type=reset],
input[type=submit] {
  width: auto;
  height: auto;
}

select,
input[type=file] {
  height: @baseline * 1.5; // In IE7, the height of the select element cannot be changed by height, only font-size
  *height: auto; // Reset for IE7
  line-height: @baseline * 1.5;
  *margin-top: 4px; /* For IE7, add top margin to align select with labels */
}

// Make multiple select elements height not fixed
select[multiple] {
  height: inherit;
  background-color: @white; // Fixes Chromium bug of unreadable items
}

textarea {
  height: auto;
}

// For text that needs to appear as an input but should not be an input
.uneditable-input {
  background-color: @white;
  display: block;
  border-color: #eee;
  .box-shadow(inset 0 1px 2px rgba(0,0,0,.025));
  cursor: not-allowed;
}

// Placeholder text gets special styles; can't be bundled together though for some reason
:-moz-placeholder {
  color: @grayLight;
}
::-webkit-input-placeholder {
  color: @grayLight;
}

// Focus states
input,
textarea {
  @transition: border linear .2s, box-shadow linear .2s;
  .transition(@transition);
  .box-shadow(inset 0 1px 3px rgba(0,0,0,.1));
}
input:focus,
textarea:focus {
  outline: 0;
  border-color: rgba(82,168,236,.8);
  @shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6);
  .box-shadow(@shadow);
}
input[type=file]:focus,
input[type=checkbox]:focus,
select:focus {
  .box-shadow(none); // override for file inputs
  outline: 1px dotted #666; // Selet elements don't get box-shadow styles, so instead we do outline
}

// FORM FIELD FEEDBACK STATES
// --------------------------

// Mixin for form field states
.formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) {
  // Set the text color
  > label,
  .help-block,
  .help-inline {
    color: @textColor;
  }
  // Style inputs accordingly
  input,
  textarea {
    color: @textColor;
    border-color: @borderColor;
    &:focus {
      border-color: darken(@borderColor, 10%);
      .box-shadow(0 0 6px lighten(@borderColor, 20%);
    }
  }
  // Give a small background color for input-prepend/-append
  .input-prepend .add-on,
  .input-append .add-on {
    color: @textColor;
    background-color: @backgroundColor;
    border-color: @textColor;
  }
}
// Error
form .clearfix.error {
  .formFieldState(#b94a48, #ee5f5b, lighten(#ee5f5b, 30%));
}
// Warning
form .clearfix.warning {
  .formFieldState(#c09853, #ccae64, lighten(#CCAE64, 5%));
}
// Success
form .clearfix.success {
  .formFieldState(#468847, #57a957, lighten(#57a957, 30%));
}

// Form element sizes
// TODO v2: remove duplication here and just stick to .input-[size] in light of adding .spanN sizes
.input-mini,
input.mini,
textarea.mini,
select.mini {
  width: 60px;
}
.input-small,
input.small,
textarea.small,
select.small {
  width: 90px;
}
.input-medium,
input.medium,
textarea.medium,
select.medium {
  width: 150px;
}
.input-large,
input.large,
textarea.large,
select.large {
  width: 210px;
}
.input-xlarge,
input.xlarge,
textarea.xlarge,
select.xlarge {
  width: 270px;
}
.input-xxlarge,
input.xxlarge,
textarea.xxlarge,
select.xxlarge {
  width: 530px;
}
textarea.xxlarge {
  overflow-y: auto;
}

// Grid style input sizes
// This is a duplication of the main grid .columns() mixin, but subtracts 10px to account for input padding and border
.formColumns(@columnSpan: 1) {
  display: inline-block;
  float: none;
  width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 10;
  margin-left: 0;
}
input,
textarea {
  // Default columns
  &.span1     { .formColumns(1); }
  &.span2     { .formColumns(2); }
  &.span3     { .formColumns(3); }
  &.span4     { .formColumns(4); }
  &.span5     { .formColumns(5); }
  &.span6     { .formColumns(6); }
  &.span7     { .formColumns(7); }
  &.span8     { .formColumns(8); }
  &.span9     { .formColumns(9); }
  &.span10    { .formColumns(10); }
  &.span11    { .formColumns(11); }
  &.span12    { .formColumns(12); }
  &.span13    { .formColumns(13); }
  &.span14    { .formColumns(14); }
  &.span15    { .formColumns(15); }
  &.span16    { .formColumns(16); }
}

// Disabled and read-only inputs
input[disabled],
select[disabled],
textarea[disabled],
input[readonly],
select[readonly],
textarea[readonly] {
  background-color: #f5f5f5;
  border-color: #ddd;
  cursor: not-allowed;
}

// Actions (the buttons)
.actions {
  background: #f5f5f5;
  margin-top: @baseline;
  margin-bottom: @baseline;
  padding: (@baseline - 1) 20px @baseline 150px;
  border-top: 1px solid #ddd;
  .border-radius(0 0 3px 3px);
  .secondary-action {
    float: right;
    a {
      line-height: 30px;
      &:hover {
        text-decoration: underline;
      }
    }
  }
}

// Help Text
// TODO: Do we need to set basefont and baseline here?
.help-inline,
.help-block {
  font-size: @basefont;
  line-height: @baseline;
  color: @grayLight;
}
.help-inline {
  padding-left: 5px;
  *position: relative; /* IE6-7 */
  *top: -5px; /* IE6-7 */
}

// Big blocks of help text
.help-block {
  display: block;
  max-width: 600px;
}

// Inline Fields (input fields that appear as inline objects
.inline-inputs {
  color: @gray;
  span {
    padding: 0 2px 0 1px;
  }
}

// Allow us to put symbols and text within the input field for a cleaner look
.input-prepend,
.input-append {
  input {
    .border-radius(0 3px 3px 0);
  }
  .add-on {
    position: relative;
    background: #f5f5f5;
    border: 1px solid #ccc;
    z-index: 2;
    float: left;
    display: block;
    width: auto;
    min-width: 16px;
    height: 18px;
    padding: 4px 4px 4px 5px;
    margin-right: -1px;
    font-weight: normal;
    line-height: 18px;
    color: @grayLight;
    text-align: center;
    text-shadow: 0 1px 0 @white;
    .border-radius(3px 0 0 3px);
  }
  .active {
    background: lighten(@green, 30);
    border-color: @green;
  }
}
.input-prepend {
  .add-on {
    *margin-top: 1px; /* IE6-7 */
  }
}
.input-append {
  input {
    float: left;
    .border-radius(3px 0 0 3px);
  }
  .add-on {
    .border-radius(0 3px 3px 0);
    margin-right: 0;
    margin-left: -1px;
  }
}

// Stacked options for forms (radio buttons or checkboxes)
.inputs-list {
  margin: 0 0 5px;
  width: 100%;
  li {
    display: block;
    padding: 0;
    width: 100%;
  }
  label {
    display: block;
    float: none;
    width: auto;
    padding: 0;
    margin-left: 20px;
    line-height: @baseline;
    text-align: left;
    white-space: normal;
    strong {
      color: @gray;
    }
    small {
      font-size: @basefont - 2;
      font-weight: normal;
    }
  }
  .inputs-list {
    margin-left: 25px;
    margin-bottom: 10px;
    padding-top: 0;
  }
  &:first-child {
    padding-top: 6px;
  }
  li + li {
    padding-top: 2px;
  }
  input[type=radio],
  input[type=checkbox] {
    margin-bottom: 0;
    margin-left: -20px;
    float: left;
  }
}

// Stacked forms
.form-stacked {
  padding-left: 20px;
  fieldset {
    padding-top: @baseline / 2;
  }
  legend {
    padding-left: 0;
  }
  label {
    display: block;
    float: none;
    width: auto;
    font-weight: bold;
    text-align: left;
    line-height: 20px;
    padding-top: 0;
  }
  .clearfix {
    margin-bottom: @baseline / 2;
    div.input {
      margin-left: 0;
    }
  }
  .inputs-list {
    margin-bottom: 0;
    li {
      padding-top: 0;
      label {
        font-weight: normal;
        padding-top: 0;
      }
    }
  }
  div.clearfix.error {
    padding-top: 10px;
    padding-bottom: 10px;
    padding-left: 10px;
    margin-top: 0;
    margin-left: -10px;
  }
  .actions {
    margin-left: -20px;
    padding-left: 20px;
  }
}


@accent-quote: #446fbd;
@accent-hr: #f18900;
@accent-link: #8757ad;
/* inline editor colors */
@inline-background-color-1: #eaeaea;
@inline-background-color-2: #dfe2e2;

@accent-rangeinfo: #6c71c4;
@accent-minus: #dc322f;
@accent-plus: #859900;


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

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

/*
 * Adds a "find in files" command to allow the user to find all occurances of a string in all files in
 * the project.
 *
 * The keyboard shortcut is Cmd(Ctrl)-Shift-F.
 *
 * FUTURE:
 *  - Proper UI for both dialog and results
 *  - Refactor dialog class and share with Quick File Open
 *  - Search files in working set that are *not* in the project
 *  - Handle matches that span mulitple lines
 *  - Refactor UI from functionality to enable unit testing
 *  - Cache result of getLine()
 */

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

    var Async               = require("utils/Async"),
        CommandManager      = require("command/CommandManager"),
        Commands            = require("command/Commands"),
        DocumentManager     = require("document/DocumentManager"),
        EditorManager       = require("editor/EditorManager"),
        FileIndexManager    = require("project/FileIndexManager");

    // This dialog class was mostly copied from QuickOpen. We should have a common dialog
    // class that everyone can use.

    /**
    * FindInFilesDialog class
    * @constructor
    *
    */
    function FindInFilesDialog() {
        this.closed = false;
        this.result = null; // $.Deferred
    }

    /**
    * Creates a dialog div floating on top of the current code mirror editor
    */
    FindInFilesDialog.prototype._createDialogDiv = function (template) {
        // FUTURE: consider using jQuery for all the DOM manipulation here
        var wrap = $("#editor-holder")[0];
        this.dialog = wrap.insertBefore(window.document.createElement("div"), wrap.firstChild);
        this.dialog.className = "CodeMirror-dialog";
        this.dialog.innerHTML = '
' + template + '
';
    };

    /**
    * Closes the search dialog and resolves the promise that showDialog returned
    */
    FindInFilesDialog.prototype._close = function (value) {
        if (this.closed) {
            return;
        }

        this.closed = true;
        this.dialog.parentNode.removeChild(this.dialog);
        EditorManager.focusEditor();
        this.result.resolve(value);
    };

    /**
    * Shows the search dialog
    * @param {?string} initialString Default text to prepopulate the search field with
    * @returns {$.Promise} that is resolved with the string to search for
    */
    FindInFilesDialog.prototype.showDialog = function (initialString) {
        var dialogHTML = 'Find in Files: <input type="text" id="findInFilesInput" style="width: 10em"> (Use /re/ syntax for regexp search)';
        this.result = new $.Deferred();
        this._createDialogDiv(dialogHTML);
        var $searchField = $('input#findInFilesInput');
        var that = this;

        $searchField.attr("value", initialString || "");
        $searchField.get(0).select();

        $searchField.bind("keydown", function (event) {
            if (event.keyCode === 13 || event.keyCode === 27) {  // Enter/Return key or Esc key
                event.stopPropagation();
                event.preventDefault();

                var query = $searchField.val();

                if (event.keyCode === 27) {
                    query = null;
                }

                that._close(query);
            }
        })
            .blur(function () {
                that._close(null);
            })
            .focus();

        return this.result.promise();
    };

    function _getSearchMatches(contents, queryExpr) {
        // Quick exit if not found
        if (contents.search(queryExpr) === -1) {
            return null;
        }

        var trimmedContents = contents;
        var startPos = 0;
        var matchStart;
        var matches = [];

        function getLineNum(offset) {
            return contents.substr(0, offset).split("\n").length - 1; // 0 based linenum
        }

        function getLine(lineNum) {
            // Future: cache result
            return contents.split("\n")[lineNum];
        }

        var match;
        while ((match = queryExpr.exec(contents)) !== null) {
            var lineNum = getLineNum(match.index);
            var line = getLine(lineNum);
            var ch = match.index - contents.lastIndexOf("\n", match.index) - 1;  // 0-based index
            var matchLength = match[0].length;

            // Don't store more than 200 chars per line
            line = line.substr(0, Math.min(200, line.length));

            matches.push({
                start: {line: lineNum, ch: ch},
                end: {line: lineNum, ch: ch + matchLength},
                line: line
            });
        }

        return matches;
    }

    function _showSearchResults(searchResults) {
        var $searchResultsDiv = $("#search-results");

        if (searchResults && searchResults.length) {
            var $resultTable = $("
")
                                .append("
");

            // Count the total number of matches
            var numMatches = 0;
            searchResults.forEach(function (item) {
                numMatches += item.matches.length;
            });

            // Show result summary in header
            $("#search-result-summary")
                .text(" - " + numMatches + " match" + (numMatches > 1 ? "es" : "") +
                      " in " + searchResults.length + " file" + (searchResults.length > 1 ? "s" : "") +
                     (numMatches > 100 ? " (showing the first 100 matches)" : ""));

            var resultsDisplayed = 0;

            searchResults.forEach(function (item) {
                if (item && resultsDisplayed < 100) {
                    var makeCell = function (content) {
                        return $("").html(content);
                    };

                    var esc = function (str) {
                        str = str.replace(/</g, "<");
                        str = str.replace(/>/g, ">");
                        return str;
                    };

                    var highlightMatch = function (line, start, end) {
                        return esc(line.substr(0, start)) + "" + esc(line.substring(start, end)) + "" + esc(line.substr(end));
                    };

                    // Add row for file name
                    $("
")
                        .append("File: " + item.fullPath + "	")
                        .appendTo($resultTable);

                    // Add row for each match in file
                    item.matches.forEach(function (match) {
                        if (resultsDisplayed < 100) {
                            var $row = $("
")
                                .append(makeCell(" "))      // Indent
                                .append(makeCell("line: " + (match.start.line + 1)))
                                .append(makeCell(highlightMatch(match.line, match.start.ch, match.end.ch)))
                                .appendTo($resultTable);

                            $row.click(function () {
                                CommandManager.execute(Commands.FILE_OPEN, {fullPath: item.fullPath})
                                    .done(function (doc) {
                                        // Opened document is now the current main editor
                                        EditorManager.getCurrentFullEditor().setSelection(match.start, match.end);
                                    });
                            });
                            resultsDisplayed++;
                        }
                    });

                }
            });

            $("#search-results .table-container")
                .empty()
                .append($resultTable);

            $("#search-results .close")
                .one("click", function () {
                    $searchResultsDiv.hide();
                    EditorManager.resizeEditor();
                });

            $searchResultsDiv.show();
        } else {
            $searchResultsDiv.hide();
        }

        EditorManager.resizeEditor();
    }

    function _getQueryRegExp(query) {
        // If query is a regular expression, use it directly
        var isRE = query.match(/^\/(.*)\/(g|i)*$/);
        if (isRE) {
            // Make sure the 'g' flag is set
            var flags = isRE[2] || "g";
            if (flags.search("g") === -1) {
                flags += "g";
            }
            return new RegExp(isRE[1], flags);
        }

        // Query is a string. Turn it into a case-insensitive regexp

        // Escape regex special chars
        query = query.replace(/(\(|\)|\{|\}|\[|\]|\.|\^|\$|\||\?|\+|\*)/g, "\\$1");
        return new RegExp(query, "gi");
    }

    /**
    * Displays a non-modal embedded dialog above the code mirror editor that allows the user to do
    * a find operation across all files in the project.
    */
    function doFindInFiles() {

        var dialog = new FindInFilesDialog();
        var searchResults = [];

        // Default to searching for the current selection
        var currentEditor = EditorManager.getFocusedEditor();
        var initialString = currentEditor && currentEditor.getSelectedText();

        dialog.showDialog(initialString)
            .done(function (query) {
                if (query) {
                    var queryExpr = _getQueryRegExp(query);
                    FileIndexManager.getFileInfoList("all")
                        .done(function (fileListResult) {
                            Async.doInParallel(fileListResult, function (fileInfo) {
                                var result = new $.Deferred();

                                DocumentManager.getDocumentForPath(fileInfo.fullPath)
                                    .done(function (doc) {
                                        var matches = _getSearchMatches(doc.getText(), queryExpr);

                                        if (matches && matches.length) {
                                            searchResults.push({
                                                fullPath: fileInfo.fullPath,
                                                matches: matches
                                            });
                                        }
                                        result.resolve();
                                    })
                                    .fail(function (error) {
                                        // Error reading this file. This is most likely because the file isn't a text file.
                                        // Resolve here so we move on to the next file.
                                        result.resolve();
                                    });

                                return result.promise();
                            })
                                .done(function () {
                                    console.dir(searchResults);
                                    _showSearchResults(searchResults);
                                })
                                .fail(function () {
                                    console.log("find in files failed.");
                                });
                        });
                }
            });
    }

    CommandManager.register(Commands.EDIT_FIND_IN_FILES, doFindInFiles);
});


* {
    color: "red";
}

.firstGrade {
    color: "white";
}

p {
    color: "blue";
}

#brack3ts {
    color: "yellow";
}


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


EditorManager   = require("editor/EditorManager"),

HighlightAgent  = require("LiveDevelopment/Agents/HighlightAgent"),

* @param Document the source document from Brackets

var CSSDocument = function CSSDocument(doc, editor) {

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

* @param {!Document} doc The source document from Brackets

* @param {!Editor} editor The editor for this document

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

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

/**
 * JSDocument manages a single JavaScript source document
 *
 * # EDITING
 *
 * Editing the document will cause the script to be reloaded via the
 * ScriptAgent, which updates the implementation of all functions without
 * loosing any state. To support redrawing canvases, jQuery must be loaded
 * and a rerender method must be attached to every canvas that clears and
 * renders the canvas.
 *
 * # HIGHLIGHTING
 *
 * JSDocument supports highlighting nodes from the HighlightAgent. Support
 * for highlighting the nodes that were created / touched by the current
 * line is missing.
 */
define(function JSDocumentModule(require, exports, module) {
    "use strict";

    var Inspector = require("LiveDevelopment/Inspector/Inspector");
    var ScriptAgent = require("LiveDevelopment/Agents/ScriptAgent");
    var HighlightAgent = require("LiveDevelopment/Agents/HighlightAgent");

    /** Constructor
     *
     * @param {Document} the source document
     */
    var JSDocument = function JSDocument(doc, editor) {
        if (!editor) {
            return;
        }
        this.doc = doc;
        this.editor = editor;
        this.onHighlight = this.onHighlight.bind(this);
        this.onChange = this.onChange.bind(this);
        this.onCursorActivity = this.onCursorActivity.bind(this);
        $(HighlightAgent).on("highlight", this.onHighlight);
        $(this.editor).on("change", this.onChange);
        $(this.editor).on("cursorActivity", this.onCursorActivity);
        this.onCursorActivity();
    };

    /** Close the document */
    JSDocument.prototype.close = function close() {
        if (!this.editor) {
            return;
        }
        $(HighlightAgent).off("highlight", this.onHighlight);
        $(this.editor).off("change", this.onChange);
        $(this.editor).off("cursorActivity", this.onCursorActivity);
        this.onHighlight();
    };

    JSDocument.prototype.script = function script() {
        return ScriptAgent.scriptForURL(this.doc.url);
    };

    /** Event Handlers *******************************************************/

    /** Triggered on cursor activity by the editor */
    JSDocument.prototype.onCursorActivity = function onCursorActivity(event, editor) {
    };

    /** Triggered on change by the editor */
    JSDocument.prototype.onChange = function onChange(event, editor, change) {
        var src = this.doc.getText();
        Inspector.Debugger.setScriptSource(this.script().scriptId, src, function onSetScriptSource(res) {
            Inspector.Runtime.evaluate("if($)$(\"canvas\").each(function(i,e){if(e.rerender)e.rerender()})");
        }.bind(this));
    };

    /** Triggered by the HighlightAgent to highlight a node in the editor */
    JSDocument.prototype.onHighlight = function onHighlight(event, node) {
        // clear an existing highlight
        var codeMirror = this.editor._codeMirror;
        var i;
        for (i in this._highlight) {
            codeMirror.removeLineClass(this._highlight[i], "wrap", "highlight");
        }
        this._highlight = [];
        if (!node || !node.trace) {
            return;
        }

        // go through the trace and find highlight the lines of this script
        var scriptId = this.script().scriptId;
        var callFrame, line;
        for (i in node.trace) {
            callFrame = node.trace[i];
            if (callFrame.location && callFrame.location.scriptId === scriptId) {
                line = callFrame.location.lineNumber;
                codeMirror.addLineClass(line, "wrap", "highlight");
                this._highlight.push(line);
            }
        }
    };

    // Export the class
    module.exports = JSDocument;
});


#!/usr/bin/env node
var far = require('far').create();

far.add(__dirname);
far.include(/test-.*\.js$/);

far.execute();


/*!
 * jQuery UI Tooltip @VERSION
 *
 * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.widget.js
 *	jquery.ui.position.js
 */
(function( $ ) {

var increments = 0;

function addDescribedBy( elem, id ) {
	var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
	describedby.push( id );
	elem
		.data( "ui-tooltip-id", id )
		.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
}

function removeDescribedBy( elem ) {
	var id = elem.data( "ui-tooltip-id" ),
		describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
		index = $.inArray( id, describedby );
	if ( index !== -1 ) {
		describedby.splice( index, 1 );
	}

	elem.removeData( "ui-tooltip-id" );
	describedby = $.trim( describedby.join( " " ) );
	if ( describedby ) {
		elem.attr( "aria-describedby", describedby );
	} else {
		elem.removeAttr( "aria-describedby" );
	}
}

$.widget( "ui.tooltip", {
	version: "@VERSION",
	options: {
		content: function() {
			return $( this ).attr( "title" );
		},
		hide: true,
		items: "[title]",
		position: {
			my: "left+15 center",
			at: "right center",
			collision: "flipfit flipfit"
		},
		show: true,
		tooltipClass: null,

		// callbacks
		close: null,
		open: null
	},

	_create: function() {
		this._bind({
			mouseover: "open",
			focusin: "open"
		});

		// IDs of generated tooltips, needed for destroy
		this.tooltips = {};
	},

	_setOption: function( key, value ) {
		if ( key === "disabled" ) {
			this[ value ? "_disable" : "_enable" ]();
			this.options[ key ] = value;
			// disable element style changes
			return;
		}
		this._super( key, value );
	},

	_disable: function() {
		var that = this;

		// close open tooltips
		$.each( this.tooltips, function( id, element ) {
			var event = $.Event( "blur" );
			event.target = event.currentTarget = element[0];
			that.close( event, true );
		});

		// remove title attributes to prevent native tooltips
		this.element.find( this.options.items ).andSelf().each(function() {
			var element = $( this );
			if ( element.is( "[title]" ) ) {
				element
					.data( "ui-tooltip-title", element.attr( "title" ) )
					.attr( "title", "" );
			}
		});
	},

	_enable: function() {
		// restore title attributes
		this.element.find( this.options.items ).andSelf().each(function() {
			var element = $( this );
			if ( element.data( "ui-tooltip-title" ) ) {
				element.attr( "title", element.data( "ui-tooltip-title" ) );
			}
		});
	},

	open: function( event ) {
		var content,
			that = this,
			target = $( event ? event.target : this.element )
				.closest( this.options.items );

		// if ui-tooltip-id exists, then the tooltip is already open
		if ( !target.length || target.data( "ui-tooltip-id" ) ) {
			return;
		}

		if ( target.attr( "title" ) ) {
			target.data( "ui-tooltip-title", target.attr( "title" ) );
		}

		target.data( "tooltip-open", true );

		content = this.options.content.call( target[0], function( response ) {
			// ignore async response if tooltip was closed already
			if ( !target.data( "tooltip-open" ) ) {
				return;
			}
			// IE may instantly serve a cached response for ajax requests
			// delay this call to _open so the other call to _open runs first
			setTimeout(function() {
				that._open( event, target, response );
			}, 1 );
		});
		if ( content ) {
			that._open( event, target, content );
		}
	},

	_open: function( event, target, content ) {
		if ( !content ) {
			return;
		}

		// if we have a title, clear it to prevent the native tooltip
		// we have to check first to avoid defining a title if none exists
		// (we don't want to cause an element to start matching [title])
		//
		// We use removeAttr only for key events, to allow IE to export the correct
		// accessible attributes. For mouse events, set to empty string to avoid
		// native tooltip showing up (happens only when removing inside mouseover).
		if ( target.is( "[title]" ) ) {
			if ( event && event.type === "mouseover" ) {
				target.attr( "title", "" );
			} else {
				target.removeAttr( "title" );
			}
		}

		// ajaxy tooltip can update an existing one
		var tooltip = this._find( target );
		if ( !tooltip.length ) {
			tooltip = this._tooltip( target );
			addDescribedBy( target, tooltip.attr( "id" ) );
		}
		tooltip.find( ".ui-tooltip-content" ).html( content );
		tooltip
			.stop( true )
			.position( $.extend({
				of: target
			}, this.options.position ) )
			.hide();

		this._show( tooltip, this.options.show );

		this._trigger( "open", event, { tooltip: tooltip } );

		this._bind( target, {
			mouseleave: "close",
			focusout: "close",
			keyup: function( event ) {
				if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
					var fakeEvent = $.Event(event);
					fakeEvent.currentTarget = target[0];
					this.close( fakeEvent, true );
				}
			}
		});
	},

	close: function( event, force ) {
		var that = this,
			target = $( event ? event.currentTarget : this.element ),
			tooltip = this._find( target );

		// disabling closes the tooltip, so we need to track when we're closing
		// to avoid an infinite loop in case the tooltip becomes disabled on close
		if ( this.closing ) {
			return;
		}

		// don't close if the element has focus
		// this prevents the tooltip from closing if you hover while focused
		//
		// we have to check the event type because tabbing out of the document
		// may leave the element as the activeElement
		if ( !force && event && event.type !== "focusout" &&
				this.document[0].activeElement === target[0] ) {
			return;
		}

		// only set title if we had one before (see comment in _open())
		if ( target.data( "ui-tooltip-title" ) ) {
			target.attr( "title", target.data( "ui-tooltip-title" ) );
		}

		removeDescribedBy( target );

		tooltip.stop( true );
		this._hide( tooltip, this.options.hide, function() {
			$( this ).remove();
			delete that.tooltips[ this.id ];
		});

		target.removeData( "tooltip-open" );
		target.unbind( "mouseleave.tooltip focusout.tooltip keyup.tooltip" );

		this.closing = true;
		this._trigger( "close", event, { tooltip: tooltip } );
		this.closing = false;
	},

	_tooltip: function( element ) {
		var id = "ui-tooltip-" + increments++,
			tooltip = $( "<div>" )
				.attr({
					id: id,
					role: "tooltip"
				})
				.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
					( this.options.tooltipClass || "" ) );
		$( "<div>" )
			.addClass( "ui-tooltip-content" )
			.appendTo( tooltip );
		tooltip.appendTo( this.document[0].body );
		if ( $.fn.bgiframe ) {
			tooltip.bgiframe();
		}
		this.tooltips[ id ] = element;
		return tooltip;
	},

	_find: function( target ) {
		var id = target.data( "ui-tooltip-id" );
		return id ? $( "#" + id ) : $();
	},

	_destroy: function() {
		$.each( this.tooltips, function( id ) {
			$( "#" + id ).remove();
		});
	}
});

}( jQuery ) );


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

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define: false, describe: false, beforeEach: false, afterEach: false, it: false, runs: false, waitsFor: false, expect: false, $: false */

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

    // Load dependent modules
    var HTMLUtils       = require("language/HTMLUtils"),
        SpecRunnerUtils = require("spec/SpecRunnerUtils"),
        Editor          = require("editor/Editor").Editor;

    describe("HTMLUtils CodeHints", function () {

        //Use a clean version of the editor each time
        var myDocument,
            myEditor;

        beforeEach(function () {
            // init Editor instance (containing a CodeMirror instance)
            $("body").append("
");
            myDocument = SpecRunnerUtils.createMockDocument("");
            myEditor = new Editor(myDocument, true, $("#editor").get(0));
        });

        afterEach(function () {
            myEditor.destroy();
            myEditor = null;
            $("#editor").remove();
            myDocument = null;
        });

        function setContentAndUpdatePos(pos, linesBefore, hintLineBefore, hintLineAfter, linesAfter) {
            pos.line = linesBefore.length;
            pos.ch = hintLineBefore.length;
            var finalHintLine = (hintLineAfter ? hintLineBefore + hintLineAfter : hintLineBefore);
            var finalLines = linesBefore.concat([finalHintLine]);
            if (linesAfter) {
                finalLines = finalLines.concat(linesAfter);
            }

            var content = finalLines.join("\n");
            myDocument.setText(content);
        }

        describe("Html Hinting", function () {
            beforeEach(function () {
                // tell CodeMirror this is html content as the mode is
                //used in determining the hints
                myEditor._codeMirror.setOption("mode", "text/x-brackets-html");
            });

            it("should not find attribute hints in an empty editor", function () {
                var pos = {"ch": 0, "line": 0};
                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo());
            });

            it("should find an attribute as a tag is getting typed", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "<p class='");

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.ATTR_VALUE, 0, "p", "class", "", true, "'", false));
            });

            it("should find an attribute as it's added to a tag", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "
"],
                    "
test
",
                    [ "
", "", ""]);

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.ATTR_VALUE, 0, "p", "id", "", true, "'", false));
            });

            it("should find an attribute as the value is typed", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "
"],
                    "
test
",
                    [ "
", "", ""]);

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.ATTR_VALUE, 3, "p", "id", "one", true, "'", false));
            });

            it("should not find an attribute as text is added", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "
tricky='", "
",
                    [ "", ""]);

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo());
            });

            it("should find the attribute value if present", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "

",
                    [ "", ""]);

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.ATTR_VALUE, 3, "p", "class", "foo", true, "'", true));
            });

            it("should find the full attribute as an existing value is changed", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "

",
                    [ "", ""]);

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.ATTR_VALUE, 3, "p", "class", "foo bar", true, "'", true));
            });

            it("should find the attribute value even when there is space around the =", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "

",
                    [ "", ""]);

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.ATTR_VALUE, 3, "p", "class", "foo", true, "'", true));
            });

            it("should find the attribute value when the IP is after the =", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "

",
                    [ "", ""]);

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.ATTR_VALUE, -1, "p", "class", "foo", true, "'", true));
            });

            it("should find the tagname as it's typed", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "<di");

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.TAG_NAME, 2, "di"));
            });

            it("should hint tagname as the open < is typed", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "
test
<");

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.TAG_NAME));
            });

            it("should find the tagname of the current tag if two tags are right next to each other", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "
<span");

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.TAG_NAME, 4, "span"));
            });

            it("should hint attributes even if there is a lot of space between the tag name and the next attr name", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "
<li  ", "  id='foo'");

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.ATTR_NAME, 0, "li"));
            });

            it("should find the tagname as space is typed before the attr name is added", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "
<span ");

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.ATTR_NAME, 0, "span"));
            });

            it("should not hint anything after the tag is closed", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "
");

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo());
            });

            it("should not hint anything after a closing tag", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "
", "
",
                    ["", ""]);

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo());
            });

            it("should not hint anything inside a closing tag", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "

"],
                    "</ht", "ml>");

                var tag = HTMLUtils.getTagInfo(myEditor, pos);
                expect(tag).toEqual(HTMLUtils.createTagInfo(HTMLUtils.CLOSING_TAG, 2, "html"));
            });

            it("should not find attributes in an empty editor", function () {
                var pos = {"ch": 0, "line": 0};
                var attrs = HTMLUtils.getTagAttributes(myEditor, pos);
                expect(attrs).toEqual([]);
            });

            it("should not find attributes before the tag is opened", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "
"],
                    "", "
test
",
                    [ "
", "", ""]);
                var attrs = HTMLUtils.getTagAttributes(myEditor, pos);
                expect(attrs).toEqual([]);
            });

            it("should not find attributes if there isn't a valid tag", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "
"],
                    "<", " id='pid' class='pclass' lang='plang' align='palign' title='ptitle'>test
",
                    [ "
", "", ""]);
                var attrs = HTMLUtils.getTagAttributes(myEditor, pos);
                expect(attrs).toEqual([]);
            });

            it("should not find attributes after the tag is closed", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "
"],
                    "
", "test
",
                    [ "
", "", ""]);
                var attrs = HTMLUtils.getTagAttributes(myEditor, pos);
                expect(attrs).toEqual([]);
            });

            it("should find all the tag attributes immediately after the tag", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "
"],
                    "
test
",
                    [ "
", "", ""]);
                var attrs = HTMLUtils.getTagAttributes(myEditor, pos);
                expect(attrs.sort()).toEqual(["id", "class", "lang", "align", "title"].sort());
            });

            it("should find all the tag attributes before closing the tag", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "
"],
                    "
test
",
                    [ "
", "", ""]);
                var attrs = HTMLUtils.getTagAttributes(myEditor, pos);
                expect(attrs.sort()).toEqual(["id", "class", "lang", "align", "title"].sort());
            });

            it("should find all the tag attributes backward and forward", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "
"],
                    "
test
",
                    [ "
", "", ""]);
                var attrs = HTMLUtils.getTagAttributes(myEditor, pos);
                expect(attrs.sort()).toEqual(["id", "class", "lang", "align", "title"].sort());
            });

            it("should find valid attributes marked as errors by the tokenizer", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "
"],
                    "
test
",
                    [ "
", "", ""]);
                var attrs = HTMLUtils.getTagAttributes(myEditor, pos);
                expect(attrs.sort()).toEqual(["id", "class", "lang", "align", "title"].sort());
            });

            it("should not find attributes in nested tags", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", "", "
"],
                    "

",
                    [ "
", "", ""]);
                var attrs = HTMLUtils.getTagAttributes(myEditor, pos);
                expect(attrs.sort()).toEqual(["id", "class", "lang", "align", "title"].sort());
            });

            it("should not find attributes of other tags on an opened tag", function () {
                var pos = {"ch": 0, "line": 0};
                setContentAndUpdatePos(pos,
                    ["", ""],
                    "<div ", "",
                    ["
", "
", "", ""]);
                var attrs = HTMLUtils.getTagAttributes(myEditor, pos);
                expect(attrs).toEqual([]);
            });
        });
    });
});