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

/*
 * This file only exists because jsTree insists on loading a CSS theme file itself.
 *
 * See jsTreeTheme.less for our actual theme for the jsTree control.
 */


/*
 * 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, $ */

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

    var Commands                = require("command/Commands"),
        CommandManager          = require("command/CommandManager"),
        SidebarView             = require("project/SidebarView"),
        ProjectManager          = require("project/ProjectManager");

    function _handleHideSidebar() {
        SidebarView.toggleSidebar();
    }

    CommandManager.register(Commands.VIEW_HIDE_SIDEBAR, _handleHideSidebar);
});


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


#testId {
	background-color:rgba(153,0,0,.5);
}

.testClass {
	color:rgba(0,255,0,.8);
}


/*
 * 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, it: false, xit: false, expect: false, beforeEach: false, afterEach: false, waitsFor: false, waits: false, runs: false, $: false*/

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

    var SpecRunnerUtils = require("./SpecRunnerUtils.js"),
        NativeApp,      //The following are all loaded from the test window
        LiveDevelopment,
        Inspector,
        DocumentManager;

    var testPath = SpecRunnerUtils.getTestPath("/spec/LiveDevelopment-test-files"),
        userDataPath = SpecRunnerUtils.getTestPath("/spec/LiveDevelopment-chrome-user-data"),
        testWindow,
        allSpacesRE = /\s+/gi;

    function fixSpaces(str) {
        return str.replace(allSpacesRE, " ");
    }

    function isOpenInBrowser(doc, agents) {
        return (doc && doc.url && agents && agents.network && agents.network.wasURLRequested(doc.url));
    }

    describe("Live Development", function () {

        beforeEach(function () {
            runs(function () {
                SpecRunnerUtils.createTestWindowAndRun(this, function (w) {
                    testWindow          = w;
                    LiveDevelopment     = testWindow.brackets.test.LiveDevelopment;
                    Inspector           = testWindow.brackets.test.Inspector;
                    DocumentManager     = testWindow.brackets.test.DocumentManager;
                    NativeApp           = testWindow.brackets.test.NativeApp;
                    NativeApp._setLiveBrowserUserDataDir(userDataPath);
                });

                SpecRunnerUtils.loadProjectInTestWindow(testPath);
            });
        });

        afterEach(function () {
            var browserDone = false;
            runs(function () {
                LiveDevelopment.close();
            });
            waitsFor(function () { return !Inspector.connected(); }, "Waiting for to close inspector", 10000);
            waits(20);
            NativeApp._setLiveBrowserUserDataDir("");

            runs(function () {
                NativeApp.closeAllLiveBrowsers().always(function () {
                    browserDone = true;
                });
                SpecRunnerUtils.closeTestWindow();

            });
            waits(100);
            //waitsFor(function () { return browserDone; }, "closeLiveBrowser timeout", 10000);
        });

        describe("CSS Editing", function () {

            it("should establish a browser connection for an opened html file", function () {
                //verify we aren't currently connected
                expect(Inspector.connected()).toBeFalsy();

                //open a file
                var htmlOpened = false;
                runs(function () {
                    SpecRunnerUtils.openProjectFiles(["simple1.html"]).fail(function () {
                        expect("Failed To Open").toBe("simple1.html");
                    }).always(function () {
                        htmlOpened = true;
                    });
                });
                waitsFor(function () { return htmlOpened; }, "htmlOpened FILE_OPEN timeout", 1000);

                //start the connection
                runs(function () {
                    LiveDevelopment.open();
                });
                waitsFor(function () { return Inspector.connected(); }, "Waiting for browser", 10000);

                runs(function () {
                    expect(Inspector.connected()).toBeTruthy();

                    var doc = DocumentManager.getOpenDocumentForPath(testPath + "/simple1.html");
                    //expect(isOpenInBrowser(doc, LiveDevelopment.agents)).toBeTruthy();
                });

                // Let things settle down before trying to close the connection.
                waits(1000);
            });

            it("should should not start a browser connection for an opened css file", function () {
                //verify we aren't currently connected
                expect(Inspector.connected()).toBeFalsy();

                //open a file
                var opened = false;
                runs(function () {
                    SpecRunnerUtils.openProjectFiles(["simple1.css"]).fail(function () {
                        expect("Failed To Open").toBe("simple1.css");
                    }).always(function () {
                        opened = true;
                    });
                });
                waitsFor(function () { return opened; }, "FILE_OPEN timeout", 1000);

                //start the connection
                runs(function () {
                    LiveDevelopment.open();
                });

                //we just need to wait an arbitrary time since we can't check for the connection to be true
                waits(1000);

                runs(function () {
                    expect(Inspector.connected()).toBeFalsy();

                    var doc = DocumentManager.getOpenDocumentForPath(testPath + "/simple1.css");
                    expect(isOpenInBrowser(doc, LiveDevelopment.agents)).toBeFalsy();
                });
            });

            it("should push changes through the browser connection", function () {
                var localText,
                    browserText;

                //verify we aren't currently connected
                expect(Inspector.connected()).toBeFalsy();

                var htmlOpened = false;
                runs(function () {
                    SpecRunnerUtils.openProjectFiles(["simple1.html"]).fail(function () {
                        expect("Failed To Open").toBe("simple1.html");
                    }).always(function () {
                        htmlOpened = true;
                    });
                });
                waitsFor(function () { return htmlOpened; }, "htmlOpened FILE_OPEN timeout", 1000);

                //start the connection
                runs(function () {
                    LiveDevelopment.open();
                });
                waitsFor(function () { return Inspector.connected(); }, "Waiting for browser", 10000);

                // Wait for the file and its stylesheets to fully load (and be communicated back).
                waits(1000);

                var cssOpened = false;
                runs(function () {
                    SpecRunnerUtils.openProjectFiles(["simple1.css"]).fail(function () {
                        expect("Failed To Open").toBe("simple1.css");
                    }).always(function () {
                        cssOpened = true;
                    });
                });
                waitsFor(function () { return cssOpened; }, "cssOpened FILE_OPEN timeout", 1000);

                runs(function () {
                    var curDoc =  DocumentManager.getCurrentDocument();
                    localText = curDoc.getText();
                    localText += "\n .testClass { color:#090; }\n";
                    curDoc.setText(localText);
                });

                //add a wait for the change to get pushed, then wait to get the result
                waits(1000);

                var doneSyncing = false;
                runs(function () {
                    var liveDoc = LiveDevelopment.getLiveDocForPath(testPath + "/simple1.css");
                    liveDoc.getSourceFromBrowser().done(function (text) {
                        browserText = text;
                    }).always(function () {
                        doneSyncing = true;
                    });
                });
                waitsFor(function () { return doneSyncing; }, "Browser to sync changes", 10000);

                runs(function () {
                    expect(fixSpaces(browserText)).toBe(fixSpaces(localText));

                    var doc = DocumentManager.getOpenDocumentForPath(testPath + "/simple1.html");
                    //expect(isOpenInBrowser(doc, LiveDevelopment.agents)).toBeTruthy();
                });
            });

            it("should push in memory css changes made before the session starts", function () {
                var localText,
                    browserText;

                //verify we aren't currently connected
                expect(Inspector.connected()).toBeFalsy();

                var cssOpened = false;
                runs(function () {
                    SpecRunnerUtils.openProjectFiles(["simple1.css"]).fail(function () {
                        expect("Failed To Open").toBe("simple1.css");
                    }).always(function () {
                        cssOpened = true;
                    });
                });
                waitsFor(function () { return cssOpened; }, "cssOpened FILE_OPEN timeout", 1000);

                runs(function () {
                    var curDoc =  DocumentManager.getCurrentDocument();
                    localText = curDoc.getText();
                    localText += "\n .testClass { color:#090; }\n";
                    curDoc.setText(localText);
                });

                var htmlOpened = false;
                runs(function () {
                    SpecRunnerUtils.openProjectFiles(["simple1.css", "simple1.html"]).fail(function () {
                        expect("Failed To Open").toBe("simple1.html");
                    }).always(function () {
                        htmlOpened = true;
                    });
                });
                waitsFor(function () { return htmlOpened; }, "htmlOpened FILE_OPEN timeout", 1000);

                //start the connection
                runs(function () {
                    LiveDevelopment.open();
                });
                waitsFor(function () { return Inspector.connected(); }, "Waiting for browser", 10000);

                //wait again for the final changes to load
                waits(350);

                var doneSyncing = false;
                runs(function () {
                    var liveDoc = LiveDevelopment.getLiveDocForPath(testPath + "/simple1.css");
                    liveDoc.getSourceFromBrowser().done(function (text) {
                        browserText = text;
                    }).always(function () {
                        doneSyncing = true;
                    });
                });
                waitsFor(function () { return doneSyncing; }, "Browser to sync changes", 10000);

                runs(function () {
                    expect(fixSpaces(browserText)).toBe(fixSpaces(localText));
                });
            });
        });
    });
});


case "cp":
case "cpp":
case "h":
case "hpp":


/*
 * 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, $ */

/**
 * Utilities for determining the current "build number" / version
 */
define(function (require, exports, module) {
    'use strict';

    var NativeFileSystem    = require("file/NativeFileSystem").NativeFileSystem,
        FileUtils           = require("file/FileUtils");

    var _bracketsSHA = null;
    var _bracketsAppSHA = null;

    /**
     * @return {?string} the Git SHA of the brackets submodule at the time when Brackets launched,
     *      or null if no Git metadata was found on disk.
     */
    function getBracketsSHA() {
        return _bracketsSHA;
    }

    /**
     * @return {?string} the Git SHA of the brackets-app module at the time when Brackets launched,
     *      or null if no Git metadata was found on disk.
     */
    function getBracketsAppSHA() {
        return _bracketsAppSHA;
    }

    /**
     * Loads a SHA from Git metadata file. If the file contains a symbolic ref name, follows the ref
     * and loads the SHA from that file in turn.
     */
    function _loadSHA(path, callback) {
        var fileEntry = new NativeFileSystem.FileEntry(path);
        var reader = new NativeFileSystem.FileReader();

        var result = new $.Deferred();

        // HEAD contains a SHA in detached-head mode; otherwise it contains a relative path
        // to a file in /refs which in turn contains the SHA
        fileEntry.file(function (file) {
            reader.onload = function (event) {
                var text = event.target.result;

                if (text.indexOf("ref: ") === 0) {
                    var basePath = path.substr(0, path.lastIndexOf("/"));
                    var refRelPath = text.substr(5).trim();
                    _loadSHA(basePath + "/" + refRelPath, callback)
                        .pipe(result.resolve, result.reject);
                } else {
                    result.resolve(text);
                }
            };
            reader.onerror = function (event) {
                result.reject();
            };

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

        return result.promise();
    }

    function init() {
        // Look for Git metadata on disk to load the SHAs for 'brackets' and 'brackets-app'. Done on
        // startup instead of on demand because the version that's currently running is what was
        // loaded at startup (the src on disk may be updated to a different version later).
        // Git metadata may be missing (e.g. in the per-sprint ZIP builds) - silently ignore if so.
        var bracketsSrc = FileUtils.getNativeBracketsDirectoryPath();
        var bracketsGitRoot = bracketsSrc + "/../../.git/";
        var bracketsSubmoduleRoot_inParent = bracketsGitRoot + "modules/brackets/";
        var bracketsSubmoduleRoot_inSubmodule = bracketsSrc + "/../.git/";

        _loadSHA(bracketsGitRoot + "HEAD")
            .done(function (text) {
                _bracketsAppSHA = text;
            });

        // brackets submodule metadata may be in brackets/.git OR a subfolder of brackets-app/.git,
        // so try both locations
        _loadSHA(bracketsSubmoduleRoot_inSubmodule + "HEAD")
            .done(function (text) {
                _bracketsSHA = text;
            })
            .fail(function () {
                _loadSHA(bracketsSubmoduleRoot_inParent + "HEAD")
                    .done(function (text) {
                        _bracketsSHA = text;
                    });
            });
    }

    // Define public API
    exports.init                = init;
    exports.getBracketsSHA      = getBracketsSHA;
    exports.getBracketsAppSHA   = getBracketsAppSHA;
});


/* Variables.less
 * Variables to customize the look and feel of Bootstrap
 * ----------------------------------------------------- */

// Links
@linkColor:         #0069d6;
@linkColorHover:    darken(@linkColor, 15);

// Grays
@black:             #000;
@grayDark:          lighten(@black, 25%);
@gray:              lighten(@black, 50%);
@grayLight:         lighten(@black, 75%);
@grayLighter:       lighten(@black, 90%);
@white:             #fff;

// Accent Colors
@blue:              #049CDB;
@blueDark:          #0064CD;
@green:             #46a546;
@red:               #9d261d;
@yellow:            #ffc40d;
@orange:            #f89406;
@pink:              #c3325f;
@purple:            #7a43b6;

// Baseline grid
@basefont:          13px;
@baseline:          18px;

// Griditude
// Modify the grid styles in mixins.less
@gridColumns:       16;
@gridColumnWidth:   40px;
@gridGutterWidth:   20px;
@extraSpace:        (@gridGutterWidth * 2); // For our grid calculations
@siteWidth:         (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1));

// Color Scheme
// Use this to roll your own color schemes if you like (unused by Bootstrap by default)
@baseColor:         @blue;                  // Set a base color
@complement:        spin(@baseColor, 180);  // Determine a complementary color
@split1:            spin(@baseColor, 158);  // Split complements
@split2:            spin(@baseColor, -158);
@triad1:            spin(@baseColor, 135);  // Triads colors
@triad2:            spin(@baseColor, -135);
@tetra1:            spin(@baseColor, 90);   // Tetra colors
@tetra2:            spin(@baseColor, -90);
@analog1:           spin(@baseColor, 22);   // Analogs colors
@analog2:           spin(@baseColor, -22);

// More variables coming soon:
// - @basefont to @baseFontSize
// - @baseline to @baseLineHeight
// - @baseFontFamily
// - @primaryButtonColor
// - anything else? File an issue on GitHub


/*
 * 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, brackets: false */

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

    // Load dependent modules
    var FileIndexManager,
        ProjectManager,
        SpecRunnerUtils     = require("./SpecRunnerUtils.js");

    describe("FileIndexManager", function () {

        var testPath = SpecRunnerUtils.getTestPath("/spec/FileIndexManager-test-files");
        var brackets;

        beforeEach(function () {

            runs(function () {
                SpecRunnerUtils.createTestWindowAndRun(this, function (testWindow) {
                    brackets = testWindow.brackets;

                    // Load module instances from brackets.test
                    FileIndexManager  = testWindow.brackets.test.FileIndexManager;
                    ProjectManager = testWindow.brackets.test.ProjectManager;

                });
            });

        });

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

        it("should index files in directory", function () {
            // Open a directory
            SpecRunnerUtils.loadProjectInTestWindow(testPath);

            var allFiles, cssFiles;
            runs(function () {
                FileIndexManager.getFileInfoList("all")
                    .done(function (result) {
                        allFiles = result;
                    });
            });
            waitsFor(function () { return allFiles; }, "FileIndexManager.getFileInfoList() timeout", 1000);

            runs(function () {
                FileIndexManager.getFileInfoList("css")
                    .done(function (result) {
                        cssFiles = result;
                    });
            });
            waitsFor(function () { return cssFiles; }, "FileIndexManager.getFileInfoList() timeout", 1000);

            runs(function () {
                expect(allFiles.length).toEqual(8);
                expect(cssFiles.length).toEqual(3);
            });

        });

        it("should match a specific filename and return the correct FileInfo", function () {
            // Open a directory
            SpecRunnerUtils.loadProjectInTestWindow(testPath);

            var fileList;

            runs(function () {
                FileIndexManager.getFilenameMatches("all", "file_four.css")
                    .done(function (results) {
                        fileList = results;
                    });
            });

            waitsFor(function () { return fileList; }, 1000);

            runs(function () {
                expect(fileList.length).toEqual(1);
                expect(fileList[0].name).toEqual("file_four.css");
                expect(fileList[0].fullPath).toEqual(testPath + "/file_four.css");
            });
        });

        it("should update the indicies on project change", function () {
            // Load spec/FileIndexManager-test-files
            SpecRunnerUtils.loadProjectInTestWindow(testPath);

            var allFiles;
            runs(function () {
                FileIndexManager.getFileInfoList("all")
                    .done(function (result) {
                        allFiles = result;
                    });
            });

            waitsFor(function () { return allFiles; }, "FileIndexManager.getFileInfoList() timeout", 1000);

            runs(function () {
                expect(allFiles.length).toEqual(8);
            });

            // load a subfolder in the test project
            // spec/FileIndexManager-test-files/dir1/dir2
            SpecRunnerUtils.loadProjectInTestWindow(testPath + "/dir1/dir2/");

            var dir2Files;
            runs(function () {
                FileIndexManager.getFileInfoList("all")
                    .done(function (result) {
                        dir2Files = result;
                    });
            });

            waitsFor(function () { return dir2Files; }, "FileIndexManager.getFileInfoList() timeout", 1000);

            runs(function () {
                expect(dir2Files.length).toEqual(2);
                expect(dir2Files[0].name).toEqual("file_eight.css");
                expect(dir2Files[1].name).toEqual("file_seven.js");
            });
        });

        it("should update the indicies after being marked dirty", function () {
            // Load spec/FileIndexManager-test-files
            SpecRunnerUtils.loadProjectInTestWindow(testPath);

            var allFiles; // set by checkAllFileCount

            // helper function to validate base state of 8 files
            function checkAllFileCount(fileCount) {
                var files;
                runs(function () {
                    FileIndexManager.getFileInfoList("all")
                        .done(function (result) {
                            files = result;
                        });
                });

                waitsFor(function () { return files; }, "FileIndexManager.getFileInfoList() timeout", 1000);

                runs(function () {
                    allFiles = files;
                    expect(files.length).toEqual(fileCount);
                });
            }

            // verify 8 files in base state
            checkAllFileCount(8);

            // add a temporary file to the folder
            var entry;

            // create a 9th file
            runs(function () {
                var root = ProjectManager.getProjectRoot();
                root.getFile("new-file.txt",
                             { create: true, exclusive: true },
                             function (fileEntry) { entry = fileEntry; });
            });

            waitsFor(function () { return entry; }, "getFile() timeout", 1000);

            runs(function () {
                // mark FileIndexManager dirty after new file was created
                FileIndexManager.markDirty();
            });

            // verify 9 files
            checkAllFileCount(9);

            var cleanupComplete = false;

            // verify the new file was added to the "all" index
            runs(function () {
                var filtered = allFiles.filter(function (value) {
                    return (value.name === "new-file.txt");
                });
                expect(filtered.length).toEqual(1);

                // remove the 9th file
                brackets.fs.unlink(entry.fullPath, function (err) {
                    cleanupComplete = (err === brackets.fs.NO_ERROR);
                });
            });

            // wait for the file to be deleted
            waitsFor(function () { return cleanupComplete; }, 1000);

            runs(function () {
                // mark FileIndexManager dirty after new file was deleted
                FileIndexManager.markDirty();
            });

            // verify that we're back to 8 files
            checkAllFileCount(8);

            // make sure the 9th file was removed from the index
            runs(function () {
                var filtered = allFiles.filter(function (value) {
                    return (value.name === "new-file.txt");
                });
                expect(filtered.length).toEqual(0);
            });
        });
    });
});


case "hpp":
case "vert":
case "frag":
case "java":

case "hxx":
case "h++":
case "ii":
return "text/x-c++src";
case "c":
case "h":
case "i":
return "text/x-csrc";
case "cs":
return "text/x-csharp";


/*
 * 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, $, brackets, FileError */

/**
 * FileSyncManager is a set of utilities to help track external modifications to the files and folders
 * in the currently open project.
 *
 * Currently, we look for external changes purely by checking file timestamps against the last-sync
 * timestamp recorded on Document. Later, we will use actual native directory-watching callbacks
 * instead.
 *
 * FUTURE: Whenever we have a 'project file tree model,' we should manipulate that instead of notifying
 * DocumentManager directly. DocumentManager, the tree UI, etc. then all listen to that model for changes.
 */
define(function (require, exports, module) {
    'use strict';

    // Load dependent modules
    var ProjectManager      = require("project/ProjectManager"),
        DocumentManager     = require("document/DocumentManager"),
        EditorManager       = require("editor/EditorManager"),
        Commands            = require("command/Commands"),
        CommandManager      = require("command/CommandManager"),
        Async               = require("utils/Async"),
        Dialogs             = require("widgets/Dialogs"),
        Strings             = require("strings"),
        FileUtils           = require("file/FileUtils");

    /**
     * Guard to spot re-entrancy while syncOpenDocuments() is still in progress
     * @type {boolean}
     */
    var _alreadyChecking = false;

    /**
     * If true, we should bail from the syncOpenDocuments() process and then re-run it. See
     * comments in syncOpenDocuments() for how this works.
     * @type {boolean}
     */
    var _restartPending = false;

    /** @type {Array.<Document>} */
    var toReload;
    /** @type {Array.<Document>} */
    var toClose;
    /** @type {Array.<Document>} */
    var editConflicts;
    /** @type {Array.<Document>} */
    var deleteConflicts;

    /**
     * Scans all the given Documents for changes on disk, and sorts them into four buckets,
     * populating the corresponding arrays:
     *  toReload        - changed on disk; unchanged within Brackets
     *  toClose         - deleted on disk; unchanged within Brackets
     *  editConflicts   - changed on disk; also dirty in Brackets
     *  deleteConflicts - deleted on disk; also dirty in Brackets
     *
     * @param {!Array.<Document>} docs
     * @return {$.Promise}  Resolved when all scanning done, or rejected immediately if there's any
     *      error while reading file timestamps. Errors are logged but no UI is shown.
     */
    function findExternalChanges(docs) {

        toReload = [];
        toClose = [];
        editConflicts = [];
        deleteConflicts = [];

        function checkDoc(doc) {
            var result = new $.Deferred();

            // Check file timestamp / existence
            doc.file.getMetadata(
                function (metadata) {
                    // Does file's timestamp differ from last sync time on the Document?
                    if (metadata.modificationTime.getTime() !== doc.diskTimestamp.getTime()) {
                        if (doc.isDirty) {
                            editConflicts.push(doc);
                        } else {
                            toReload.push(doc);
                        }
                    }
                    result.resolve();
                },
                function (error) {
                    // File has been deleted externally
                    if (error.code === FileError.NOT_FOUND_ERR) {
                        if (doc.isDirty) {
                            deleteConflicts.push(doc);
                        } else {
                            toClose.push(doc);
                        }
                        result.resolve();
                    } else {
                        // Some other error fetching metadata: treat as a real error
                        console.log("Error checking modification status of " + doc.file.fullPath, error.code);
                        result.reject();
                    }
                }
            );
            return result.promise();
        }

        // Check all docs in parallel
        // (fail fast b/c we won't continue syncing if there was any error fetching timestamps)
        return Async.doInParallel(docs, checkDoc, true);
    }

    /**
     * Scans all the files in the working set that do not have Documents (and thus were not scanned
     * by findExternalChanges()). If any were deleted on disk, removes them from the working set.
     */
    function syncUnopenWorkingSet() {
        // We only care about working set entries that have never been open (have no Document).
        var unopenWorkingSetFiles = DocumentManager.getWorkingSet().filter(function (wsFile) {
            return !DocumentManager.getOpenDocumentForPath(wsFile.fullPath);
        });

        function checkWorkingSetFile(file) {
            var result = new $.Deferred();

            file.getMetadata(
                function (metadata) {
                    // File still exists
                    result.resolve();
                },
                function (error) {
                    // File has been deleted externally
                    if (error.code === FileError.NOT_FOUND_ERR) {
                        DocumentManager.notifyFileDeleted(file);
                        result.resolve();
                    } else {
                        // Some other error fetching metadata: treat as a real error
                        console.log("Error checking for deletion of " + file.fullPath, error.code);
                        result.reject();
                    }
                }
            );
            return result.promise();
        }

        // Check all these files in parallel
        return Async.doInParallel(unopenWorkingSetFiles, checkWorkingSetFile, false);
    }

    /**
     * Reloads the Document's contents from disk, discarding any unsaved changes in the editor.
     *
     * @param {!Document} doc
     * @return {$.Promise} Resolved after editor has been refreshed; rejected if unable to load the
     *      file's new content. Errors are logged but no UI is shown.
     */
    function reloadDoc(doc) {

        var promise = FileUtils.readAsText(doc.file);

        promise.done(function (text, readTimestamp) {
            doc.refreshText(text, readTimestamp);
        });
        promise.fail(function (error) {
            console.log("Error reloading contents of " + doc.file.fullPath, error.code);
        });
        return promise;
    }

    /**
     * Reloads all the documents in "toReload" silently (no prompts). The operations are all run
     * in parallel.
     * @return {$.Promise} Resolved/rejected after all reloads done; will be rejected if any one
     *      file's reload failed. Errors are logged (by reloadDoc()) but no UI is shown.
     */
    function reloadChangedDocs() {
        // Reload each doc in turn, and once all are (async) done, signal that we're done
        return Async.doInParallel(toReload, reloadDoc, false);
    }

    /**
     * @param {FileError} error
     * @param {!Document} doc
     * @return {$.Promise}
     */
    function showReloadError(error, doc) {
        return Dialogs.showModalDialog(
            Dialogs.DIALOG_ID_ERROR,
            Strings.ERROR_RELOADING_FILE_TITLE,
            Strings.format(
                Strings.ERROR_RELOADING_FILE,
                doc.file.fullPath,
                FileUtils.getFileErrorString(error.code)
            )
        );
    }

    /**
     * Closes all the documents in "toClose" silently (no prompts). Completes synchronously.
     */
    function closeDeletedDocs() {
        toClose.forEach(function (doc) {
            DocumentManager.notifyFileDeleted(doc.file);
        });
    }

    /**
     * Walks through all the documents in "editConflicts" & "deleteConflicts" and prompts the user
     * about each one. Processing is sequential: if the user chooses to reload a document, the next
     * prompt is not shown until after the reload has completed.
     *
     * @return {$.Promise} Resolved/rejected after all documents have been prompted and (if
     *      applicable) reloaded (and any resulting error UI has been dismissed). Rejected if any
     *      one reload failed.
     */
    function presentConflicts() {

        var allConflicts = editConflicts.concat(deleteConflicts);

        function presentConflict(doc, i) {
            var result = new $.Deferred(), promise = result.promise();

            // If window has been re-focused, skip all remaining conflicts so the sync can bail & restart
            if (_restartPending) {
                result.resolve();
                return promise;
            }

            var message;
            var dialogId;
            var toClose;

            // Prompt UI varies depending on whether the file on disk was modified vs. deleted
            if (i < editConflicts.length) {
                toClose = false;
                dialogId = Dialogs.DIALOG_ID_EXT_CHANGED;
                message = Strings.format(
                    Strings.EXT_MODIFIED_MESSAGE,
                    ProjectManager.makeProjectRelativeIfPossible(doc.file.fullPath)
                );

            } else {
                toClose = true;
                dialogId = Dialogs.DIALOG_ID_EXT_DELETED;
                message = Strings.format(
                    Strings.EXT_DELETED_MESSAGE,
                    ProjectManager.makeProjectRelativeIfPossible(doc.file.fullPath)
                );
            }

            Dialogs.showModalDialog(dialogId, Strings.EXT_MODIFIED_TITLE, message)
                .done(function (id) {
                    if (id === Dialogs.DIALOG_BTN_DONTSAVE) {
                        if (toClose) {
                            // Discard - close all editors
                            DocumentManager.notifyFileDeleted(doc.file);
                            result.resolve();
                        } else {
                            // Discard - load changes from disk
                            reloadDoc(doc)
                                .done(function () {
                                    result.resolve();
                                })
                                .fail(function (error) {
                                    // Unable to load changed version from disk - show error UI
                                    showReloadError(error, doc)
                                        .always(function () {
                                            // After user dismisses, move on to next conflict prompt
                                            result.reject();
                                        });
                                });
                        }

                    } else {
                        // Cancel - if user doesn't manually save or close, we'll prompt again next
                        // time window is reactivated;
                        // OR programmatically canceled due to _resetPending - we'll skip all
                        // remaining files in the conflicts list (see above)
                        result.resolve();
                    }
                });

            return promise;
        }

        // Begin walking through the conflicts, one at a time
        return Async.doSequentially(allConflicts, presentConflict, false);
    }

    /**
     * Check to see whether any open files have been modified by an external app since the last time
     * Brackets synced up with the copy on disk (either by loading or saving the file). For clean
     * files, we silently upate the editor automatically. For files with unsaved changes, we prompt
     * the user.
     */
    function syncOpenDocuments() {

        // We can become "re-entrant" if the user leaves & then returns to Brackets before we're
        // done -- easy if a prompt dialog is left open. Since the user may have left Brackets to
        // revert some of the disk changes, etc. we want to cancel the current sync and immediately
        // begin a new one. We let the orig sync run until the user-visible dialog phase, then
        // bail; if we're already there we programmatically close the dialog to bail right away.
        if (_alreadyChecking) {
            _restartPending = true;

            // Close dialog if it was open. This will 'unblock' presentConflict(), which bails back
            // to us immediately upon seeing _restartPending. We then restart the sync - see below
            Dialogs.cancelModalDialogIfOpen(Dialogs.DIALOG_ID_EXT_CHANGED);
            Dialogs.cancelModalDialogIfOpen(Dialogs.DIALOG_ID_EXT_DELETED);

            return;
        }

        _alreadyChecking = true;

        // Syncing proceeds in four phases:
        //  1) Check all open files for external modifications
        //  2) Check any other working set entries (that are not open) for deletion, and remove
        //     from working set if deleted
        //  3) Refresh all Documents that are clean (if file changed on disk)
        //  4) Close all Documents that are clean (if file deleted on disk)
        //  5) Prompt about any Documents that are dirty (if file changed/deleted on disk)
        // Each phase fully completes (asynchronously) before the next one begins.

        // 1) Check for external modifications
        var allDocs = DocumentManager.getAllOpenDocuments();

        findExternalChanges(allDocs)
            .done(function () {
                // 2) Check un-open working set entries for deletion (& "close" if needed)
                syncUnopenWorkingSet()
                    .always(function () {
                        // If we were unable to check any un-open files for deletion, silently ignore
                        // (after logging to console). This doesn't have any bearing on syncing truly
                        // open Documents (which we've already successfully checked).

                        // 3) Reload clean docs as needed
                        reloadChangedDocs()
                            .always(function () {
                                // 4) Close clean docs as needed
                                // This phase completes synchronously
                                closeDeletedDocs();

                                // 5) Prompt for dirty editors (conflicts)
                                presentConflicts()
                                    .always(function () {
                                        if (_restartPending) {
                                            // Restart the sync if needed
                                            _restartPending = false;
                                            _alreadyChecking = false;
                                            syncOpenDocuments();
                                        } else {
                                            // We're really done!
                                            _alreadyChecking = false;

                                            // If we showed a dialog, restore focus to editor
                                            if (editConflicts.length > 0 || deleteConflicts.length > 0) {
                                                EditorManager.focusEditor();
                                            }

                                            // (Any errors that ocurred during presentConflicts() have already
                                            // shown UI & been dismissed, so there's no fail() handler here)
                                        }
                                    });
                            });
                            // Note: if any auto-reloads failed, we silently ignore (after logging to console)
                            // and we still continue onto phase 4 and try to process those files anyway.
                            // (We'll retry the auto-reloads next time window is activated... and evenually
                            // we'll also be double checking before each Save).
                    });
            }).fail(function () {
                // Unable to fetch timestamps for some reason - silently ignore (after logging to console)
                // (We'll retry next time window is activated... and evenually we'll also be double
                // checking before each Save).

                // We can't go on without knowing which files are dirty, so bail now
                _alreadyChecking = false;
            });

    }

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


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

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

    /**
     * List of constants for global command IDs.
     */

    // FILE
    exports.FILE_NEW             = "file.new";
    exports.FILE_OPEN           = "file.open";
    exports.FILE_OPEN_FOLDER    = "file.openFolder";
    exports.FILE_SAVE           = "file.save";
    exports.FILE_CLOSE          = "file.close";
    exports.FILE_CLOSE_ALL      = "file.close_all";
    exports.FILE_CLOSE_WINDOW   = "file.close_window"; // string must MATCH string in native code (brackets_extensions)
    exports.FILE_ADD_TO_WORKING_SET = "file.addToWorkingSet";
    exports.FILE_LIVE_FILE_PREVIEW = "file.liveFilePreview";
    exports.FILE_QUIT           = "file.quit"; // string must MATCH string in native code (brackets_extensions)

    // EDIT
    exports.EDIT_UNDO           = "edit.undo";
    exports.EDIT_REDO           = "edit.redo";
    exports.EDIT_CUT            = "edit.cut";
    exports.EDIT_COPY           = "edit.copy";
    exports.EDIT_PASTE          = "edit.paste";
    exports.EDIT_SELECT_ALL     = "edit.selectAll";
    exports.EDIT_FIND           = "edit.find";
    exports.EDIT_FIND_IN_FILES  = "edit.findInFiles";
    exports.EDIT_FIND_NEXT      = "edit.findNext";
    exports.EDIT_FIND_PREVIOUS  = "edit.findPrevious";
    exports.EDIT_REPLACE        = "edit.replace";
    exports.EDIT_INDENT         = "edit.indent";
    exports.EDIT_UNINDENT       = "edit.unindent";
    exports.EDIT_DUPLICATE      = "edit.duplicate";
    exports.EDIT_LINE_COMMENT   = "edit.lineComment";

    // VIEW
    exports.VIEW_HIDE_SIDEBAR   = "view.hideSidebar";

    // Navigate
    exports.NAVIGATE_QUICK_OPEN         = "navigate.quickOpen";
    exports.NAVIGATE_GOTO_DEFINITION    = "navigate.gotoDefinition";
    exports.NAVIGATE_GOTO_LINE          = "navigate.gotoLine";
    exports.SHOW_INLINE_EDITOR          = "navigate.showInlineEditor";
    exports.QUICK_EDIT_NEXT_MATCH       = "navigate.nextMatch";
    exports.QUICK_EDIT_PREV_MATCH       = "navigate.previousMatch";

    exports.DEBUG_REFRESH_WINDOW            = "debug.refreshWindow"; // string must MATCH string in native code (brackets_extensions)
    exports.DEBUG_SHOW_DEVELOPER_TOOLS      = "debug.showDeveloperTools";
    exports.DEBUG_RUN_UNIT_TESTS            = "debug.runUnitTests";
    exports.DEBUG_JSLINT                    = "debug.jslint";
    exports.DEBUG_SHOW_PERF_DATA            = "debug.showPerfData";
    exports.DEBUG_NEW_BRACKETS_WINDOW       = "debug.newBracketsWindow";
    exports.DEBUG_CLOSE_ALL_LIVE_BROWSERS   = "debug.closeAllLiveBrowsers";
    exports.DEBUG_USE_TAB_CHARS             = "debug.useTabChars";

    exports.HELP_ABOUT = "help.about";
});


/*
 * 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, $, CodeMirror, window */

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

    var EditorManager   = require("editor/EditorManager"),
        Commands        = require("command/Commands"),
        CommandManager  = require("command/CommandManager"),
        PerfUtils       = require("utils/PerfUtils"),
        TextRange       = require("document/TextRange").TextRange,
        ViewUtils       = require("utils/ViewUtils");

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

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

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

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

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

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

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

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

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

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

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

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

        return handled;
    }

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

                if (nonWS === -1 || nonWS >= cursor.ch) {
                    // Need to do the auto-indent on a timeout to ensure
                    // the keypress is handled before auto-indenting.
                    // This is the same timeout value used by the
                    // electricChars feature in CodeMirror.
                    window.setTimeout(function () {
                        instance.indentLine(cursor.line);
                    }, 75);
                }
            }
        }
    }

    function _handleSelectAll() {
        var editor = EditorManager.getFocusedEditor();
        if (editor) {
            editor._selectAllVisible();
        }
    }

    /** Launches CodeMirror's basic Find-within-single-editor feature */
    function _launchFind() {
        var editor = EditorManager.getFocusedEditor();
        if (editor) {
            var codeMirror = editor._codeMirror;

            // Bring up CodeMirror's existing search bar UI
            codeMirror.execCommand("find");

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

    function _findNext() {
        var editor = EditorManager.getFocusedEditor();
        if (editor) {
            editor._codeMirror.execCommand("findNext");
        }
    }

    function _findPrevious() {
        var editor = EditorManager.getFocusedEditor();
        if (editor) {
            editor._codeMirror.execCommand("findPrev");
        }
    }

    function _replace() {
        var editor = EditorManager.getFocusedEditor();
        if (editor) {
            editor._codeMirror.execCommand("replace");
        }
    }

    /**
     * List of all current (non-destroy()ed) Editor instances. Needed when changing global preferences
     * that affect all editors, e.g. tabbing or color scheme settings.
     * @type {Array.<Editor>}
     */
    var _instances = [];

    /** @type {boolean}  Global setting: When inserting new text, use tab characters? (instead of spaces) */
    var _useTabChar = false;

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

        _instances.push(this);

        // Attach to document: add ref & handlers
        this.document = document;
        document.addRef();

        if (range) {    // attach this first: want range updated before we process a change
            this._visibleRange = new TextRange(document, range.startLine, range.endLine);
        }

        // store this-bound version of listeners so we can remove them later
        this._handleDocumentChange = this._handleDocumentChange.bind(this);
        this._handleDocumentDeleted = this._handleDocumentDeleted.bind(this);
        $(document).on("change", this._handleDocumentChange);
        $(document).on("deleted", this._handleDocumentDeleted);

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

        this._inlineWidgets = [];

        // Editor supplies some standard keyboard behavior extensions of its own
        var codeMirrorKeyMap = {
            "Tab" : _handleTabKey,

            "Left" : function (instance) {
                if (!_handleSoftTabNavigation(instance, -1, "moveH")) {
                    CodeMirror.commands.goCharLeft(instance);
                }
            },
            "Right" : function (instance) {
                if (!_handleSoftTabNavigation(instance, 1, "moveH")) {
                    CodeMirror.commands.goCharRight(instance);
                }
            },
            "Backspace" : function (instance) {
                if (!_handleSoftTabNavigation(instance, -1, "deleteH")) {
                    CodeMirror.commands.delCharLeft(instance);
                }
            },
            "Delete" : function (instance) {
                if (!_handleSoftTabNavigation(instance, 1, "deleteH")) {
                    CodeMirror.commands.delCharRight(instance);
                }
            },
            "Shift-Delete": "cut",
            "Ctrl-Insert": "copy",
            "Shift-Insert": "paste"
        };

        EditorManager.mergeExtraKeys(self, codeMirrorKeyMap, additionalKeys);

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

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

        this._installEditorListeners();

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

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

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

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

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

        // Add scrollTop property to this object for the scroll shadow code to use
        Object.defineProperty(this, "scrollTop", {
            get: function () {
                return this._codeMirror.scrollPos().y;
            }
        });
    }

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

        _instances.splice(_instances.indexOf(this), 1);

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

        if (this._visibleRange) {   // TextRange also refs the Document
            this._visibleRange.dispose();
        }

        // If we're the Document's master editor, disconnecting from it has special meaning
        if (this.document._masterEditor === this) {
            this.document._makeNonEditable();
        }

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

    /**
     * Handles Select All specially when we have a visible range in order to work around
     * bugs in CodeMirror when lines are hidden.
     */
    Editor.prototype._selectAllVisible = function () {
        var startLine = this.getFirstVisibleLine(),
            endLine = this.getLastVisibleLine();
        this.setSelection({line: startLine, ch: 0},
                          {line: endLine, ch: this.getLineText(endLine).length});
    };

    Editor.prototype._applyChanges = function (changeList) {
        var self = this;

        // _visibleRange has already updated via its own Document listener. See if this change caused
        // it to lose sync. If so, our whole view is stale - signal our owner to close us.
        if (this._visibleRange) {
            if (this._visibleRange.startLine === null || this._visibleRange.endLine === null) {
                $(self).triggerHandler("lostContent");
                return;
            }
        }

        // Apply text changes to CodeMirror editor
        var cm = this._codeMirror;
        cm.operation(function () {
            var change, newText;
            for (change = changeList; change; change = change.next) {
                newText = change.text.join('\n');
                if (!change.from || !change.to) {
                    if (change.from || change.to) {
                        console.assert(false, "Change record received with only one end undefined--replacing entire text");
                    }
                    cm.setValue(newText);
                } else {
                    cm.replaceRange(newText, change.from, change.to);
                }

            }
        });

        // The update above may have inserted new lines - must hide any that fall outside our range
        if (self._visibleRange) {
            cm.operation(function () {
                // TODO: could make this more efficient by only iterating across the min-max line
                // range of the union of all changes
                var i;
                for (i = 0; i < cm.lineCount(); i++) {
                    if (i < self._visibleRange.startLine || i > self._visibleRange.endLine) {
                        self._hideLine(i);
                    } else {
                        // Double-check that the set of NON-hidden lines matches our range too
                        console.assert(!cm.getLineHandle(i).hidden);
                    }
                }
            });
        }
    };

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

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

        if (this.document._masterEditor !== this) {
            // Secondary editor:
            // we're not the ground truth; if we got here, this was a real editor change (not a
            // sync from the real ground truth), so we need to sync from us into the document
            // (which will directly push the change into the master editor).
            // FUTURE: Technically we should add a replaceRange() method to Document and go through
            // that instead of talking to its master editor directly. It's not clear yet exactly
            // what the right Document API would be, though.
            this._duringSync = true;
            this.document._masterEditor._applyChanges(changeList);
            this._duringSync = false;
        }
        // Else, Master editor:
        // we're the ground truth; nothing else to do, since Document listens directly to us
        // note: this change might have been a real edit made by the user, OR this might have
        // been a change synced from another editor

        if (this._visibleRange) {
            // _visibleRange has already updated via its own Document listener, when we pushed our
            // change into the Document above (_masterEditor._applyChanges()). But changes due to our
            // own edits should never cause the range to lose sync - verify that.
            if (this._visibleRange.startLine === null || this._visibleRange.endLine === null) {
                throw new Error("ERROR: Typing in Editor should not destroy its own _visibleRange");
            }
        }
    };

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

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

        if (this.document._masterEditor !== this) {
            // Secondary editor:
            // we're not the ground truth; and if we got here, this was a Document change that
            // didn't come from us (e.g. a sync from another editor, a direct programmatic change
            // to the document, or a sync from external disk changes)... so sync from the Document
            this._duringSync = true;
            this._applyChanges(changeList);
            this._duringSync = false;
        }
        // Else, Master editor:
        // we're the ground truth; nothing to do since Document change is just echoing our
        // editor changes
    };

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

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

        // FUTURE: if this list grows longer, consider making this a more generic mapping
        // NOTE: change is a "private" event--others shouldn't listen to it on Editor, only on
        // Document
        this._codeMirror.setOption("onChange", function (instance, changeList) {
            $(self).triggerHandler("change", [self, changeList]);
        });
        this._codeMirror.setOption("onKeyEvent", function (instance, event) {
            $(self).triggerHandler("keyEvent", [self, event]);
            return false;   // false tells CodeMirror we didn't eat the event
        });
        this._codeMirror.setOption("onCursorActivity", function (instance) {
            $(self).triggerHandler("cursorActivity", [self]);
        });
        this._codeMirror.setOption("onScroll", function (instance) {
            $(self).triggerHandler("scroll", [self]);

            // notify all inline widgets of a position change
            self._fireWidgetOffsetTopChanged(self.getFirstVisibleLine() - 1);
        });
    };

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

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

    /**
     * Sets the contents of the editor and clears the undo/redo history. Dispatches a change event.
     * Semi-private: only Document/EditableDocumentModel should call this.
     * @param {!string} text
     */
    Editor.prototype._resetText = function (text) {
        var perfTimerName = PerfUtils.markStart("Edtitor._resetText()\t" + (!this.document || this.document.file.fullPath));

        var cursorPos = this.getCursorPos(),
            scrollPos = this.getScrollPos();

        // This *will* fire a change event, but we clear the undo immediately afterward
        this._codeMirror.setValue(text);

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

        // restore cursor and scroll positions
        this.setCursorPos(cursorPos);
        this.setScrollPos(scrollPos.x, scrollPos.y);

        PerfUtils.addMeasurement(perfTimerName);
    };

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

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

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

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

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

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

    /**
     * Gets the number of the first visible line in the editor.
     * @returns {number} The 0-based index of the first visible line.
     */
    Editor.prototype.getFirstVisibleLine = function () {
        return (this._visibleRange ? this._visibleRange.startLine : 0);
    };

    /**
     * Gets the number of the last visible line in the editor.
     * @returns {number} The 0-based index of the last visible line.
     */
    Editor.prototype.getLastVisibleLine = function () {
        return (this._visibleRange ? this._visibleRange.endLine : this.lineCount() - 1);
    };

    // FUTURE change to "hideLines()" API that hides a range of lines at once in a single operation, then fires offsetTopChanged afterwards.
    /* Hides the specified line number in the editor
     * @param {!number}
     */
    Editor.prototype._hideLine = function (lineNumber) {
        var value = this._codeMirror.hideLine(lineNumber);

        // when this line is hidden, notify all following inline widgets of a position change
        this._fireWidgetOffsetTopChanged(lineNumber);

        return value;
    };

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

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

    /**
     * Gets the root DOM node of the editor.
     * @returns {Object} The editor's root DOM node.
     */
    Editor.prototype.getRootElement = function () {
        return this._codeMirror.getWrapperElement();
    };

    /**
     * Gets the lineSpace element within the editor (the container around the individual lines of code).
     * FUTURE: This is fairly CodeMirror-specific. Logic that depends on this may break if we switch
     * editors.
     * @returns {Object} The editor's lineSpace element.
     */
    Editor.prototype._getLineSpaceElement = function () {
        return $(".CodeMirror-lines", this.getScrollerElement()).children().get(0);
    };

    /**
     * Returns the current scroll position of the editor.
     * @returns {{x:number, y:number}} The x,y scroll position.
     */
    Editor.prototype.getScrollPos = function () {
        return this._codeMirror.scrollPos();
    };

    /**
     * Sets the current scroll position of the editor.
     * @param {number} x scrollLeft position
     * @param {number} y scrollTop position
     */
    Editor.prototype.setScrollPos = function (x, y) {
        this._codeMirror.scrollTo(x, y);
    };

    /**
     * Adds an inline widget below the given line. If any inline widget was already open for that
     * line, it is closed without warning.
     * @param {!{line:number, ch:number}} pos  Position in text to anchor the inline.
     * @param {!InlineWidget} inlineWidget The widget to add.
     */
    Editor.prototype.addInlineWidget = function (pos, inlineWidget) {
        var self = this;
        inlineWidget.id = this._codeMirror.addInlineWidget(pos, inlineWidget.htmlContent, inlineWidget.height, function (id) {
            self._removeInlineWidgetInternal(id);
            inlineWidget.onClosed();
        });
        this._inlineWidgets.push(inlineWidget);
        inlineWidget.onAdded();

        // once this widget is added, notify all following inline widgets of a position change
        this._fireWidgetOffsetTopChanged(pos.line);
    };

    /**
     * Removes the given inline widget.
     * @param {number} inlineWidget The widget to remove.
     */
    Editor.prototype.removeInlineWidget = function (inlineWidget) {
        var lineNum = this._getInlineWidgetLineNumber(inlineWidget);

        // _removeInlineWidgetInternal will get called from the destroy callback in CodeMirror.
        this._codeMirror.removeInlineWidget(inlineWidget.id);

        // once this widget is removed, notify all following inline widgets of a position change
        this._fireWidgetOffsetTopChanged(lineNum);
    };

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

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

    /**
     * Sets the height of an inline widget in this editor.
     * @param {!InlineWidget} inlineWidget The widget whose height should be set.
     * @param {!number} height The height of the widget.
     * @param {boolean} ensureVisible Whether to scroll the entire widget into view.
     */
    Editor.prototype.setInlineWidgetHeight = function (inlineWidget, height, ensureVisible) {
        var info = this._codeMirror.getInlineWidgetInfo(inlineWidget.id),
            oldHeight = (info && info.height) || 0;

        this._codeMirror.setInlineWidgetHeight(inlineWidget.id, height, ensureVisible);

        // update position for all following inline editors
        if (oldHeight !== height) {
            var lineNum = this._getInlineWidgetLineNumber(inlineWidget);
            this._fireWidgetOffsetTopChanged(lineNum);
        }
    };

    /**
     * @private
     * Get the starting line number for an inline widget.
     * @param {!InlineWidget} inlineWidget
     * @return {number} The line number of the widget or -1 if not found.
     */
    Editor.prototype._getInlineWidgetLineNumber = function (inlineWidget) {
        var info = this._codeMirror.getInlineWidgetInfo(inlineWidget.id);
        return (info && info.line) || -1;
    };

    /**
     * @private
     * Fire "offsetTopChanged" events when inline editor positions change due to
     * height changes of other inline editors.
     * @param {!InlineWidget} inlineWidget
     */
    Editor.prototype._fireWidgetOffsetTopChanged = function (lineNum) {
        var self = this,
            otherLineNum;

        this.getInlineWidgets().forEach(function (other) {
            otherLineNum = self._getInlineWidgetLineNumber(other);

            if (otherLineNum > lineNum) {
                $(other).triggerHandler("offsetTopChanged");
            }
        });
    };

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

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

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

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

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

    /**
     * Returns the text of the given line.
     * @param {number} The zero-based number of the line to retrieve.
     * @return {string} The contents of the line.
     */
    Editor.prototype.getLineText = function (num) {
        return this._codeMirror.getLine(num);
    };

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

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

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

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

    /**
     * @private
     * @type {?TextRange}
     */
    Editor.prototype._visibleRange = null;

    // Global settings that affect all Editor instances (both currently open Editors as well as those created
    // in the future)

    /**
     * Sets whether to use tab characters (vs. spaces) when inserting new text. Affects all Editors.
     * @param {boolean} value
     */
    Editor.setUseTabChar = function (value) {
        _useTabChar = value;
        _instances.forEach(function (editor) {
            editor._codeMirror.setOption("indentWithTabs", _useTabChar);
        });
    };

    /** @type {boolean}  Gets whether all Editors use tab characters (vs. spaces) when inserting new text */
    Editor.getUseTabChar = function (value) {
        return _useTabChar;
    };

    // Global commands that affect the currently focused Editor instance, wherever it may be
    CommandManager.register(Commands.EDIT_FIND, _launchFind);
    CommandManager.register(Commands.EDIT_FIND_NEXT, _findNext);
    CommandManager.register(Commands.EDIT_REPLACE, _replace);
    CommandManager.register(Commands.EDIT_FIND_PREVIOUS, _findPrevious);
    CommandManager.register(Commands.EDIT_SELECT_ALL, _handleSelectAll);

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


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


<!doctype html>

<meta charset="UTF-8">
<title>Simple Test</title>
<link rel="stylesheet" href="simpleShared.css">
<link rel="stylesheet" href="simple2.css">

Brackets is awesome!