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

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

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

    var Async = require("Async");

    var NativeFileSystem = {

        /**
         * Amount of time we wait for async calls to return (in milliseconds)
         * Not all async calls are wrapped with something that times out and
         * calls the error callback. Timeouts are not specified in the W3C spec.
         * @const
         * @type {number}
         */
        ASYNC_TIMEOUT: 2000,

        /** showOpenDialog
         *
         * @param {bool} allowMultipleSelection
         * @param {bool} chooseDirectories
         * @param {string} title
         * @param {string} initialPath
         * @param {Array.<string>} fileTypes
         * @param {function(...)} successCallback
         * @param {function(...)} errorCallback
         * @constructor
         */
        showOpenDialog: function (allowMultipleSelection,
                                  chooseDirectories,
                                  title,
                                  initialPath,
                                  fileTypes,
                                  successCallback,
                                  errorCallback) {
            if (!successCallback) {
                return;
            }

            var files = brackets.fs.showOpenDialog(
                allowMultipleSelection,
                chooseDirectories,
                title,
                initialPath,
                fileTypes,
                function (err, data) {
                    if (!err) {
                        successCallback(data);
                    } else if (errorCallback) {
                        errorCallback(NativeFileSystem._nativeToFileError(err));
                    }
                }
            );
        },

        /** requestNativeFileSystem
         *
         * @param {string} path
         * @param {function(...)} successCallback
         * @param {function(...)} errorCallback
         */
        requestNativeFileSystem: function (path, successCallback, errorCallback) {
            brackets.fs.stat(path, function (err, data) {
                if (!err) {
                    // FIXME (issue #247): return a NativeFileSystem object
                    var root = new NativeFileSystem.DirectoryEntry(path);
                    successCallback(root);
                } else if (errorCallback) {
                    errorCallback(NativeFileSystem._nativeToFileError(err));
                }
            });
        },

        _nativeToFileError: function (nativeErr) {
            var error;

            switch (nativeErr) {
                // We map ERR_UNKNOWN and ERR_INVALID_PARAMS to SECURITY_ERR,
                // since there aren't specific mappings for these.
            case brackets.fs.ERR_UNKNOWN:
            case brackets.fs.ERR_INVALID_PARAMS:
                error = FileError.SECURITY_ERR;
                break;
            case brackets.fs.ERR_NOT_FOUND:
                error = FileError.NOT_FOUND_ERR;
                break;
            case brackets.fs.ERR_CANT_READ:
                error = FileError.NOT_READABLE_ERR;
                break;
            // It might seem like you should use FileError.ENCODING_ERR for this,
            // but according to the spec that's for malformed URLs.
            case brackets.fs.ERR_UNSUPPORTED_ENCODING:
                error = FileError.SECURITY_ERR;
                break;
            case brackets.fs.ERR_CANT_WRITE:
                error = FileError.NO_MODIFICATION_ALLOWED_ERR;
                break;
            case brackets.fs.ERR_OUT_OF_SPACE:
                error = FileError.QUOTA_EXCEEDED_ERR;
                break;
            case brackets.fs.PATH_EXISTS_ERR:
                error = FileError.PATH_EXISTS_ERR;
                break;
            default:
                // The HTML file spec says SECURITY_ERR is a catch-all to be used in situations
                // not covered by other error codes.
                error = FileError.SECURITY_ERR;
            }
            return new NativeFileSystem.FileError(error);
        }
    };

    /** class: Entry
     *
     * @param {string} name
     * @param {string} isFile
     * @constructor
     */
    NativeFileSystem.Entry = function (fullPath, isDirectory) {
        this.isDirectory = isDirectory;
        this.isFile = !isDirectory;

        if (fullPath) {
            // add trailing "/" to directory paths
            if (isDirectory && (fullPath.charAt(fullPath.length - 1) !== '/')) {
                fullPath = fullPath.concat("/");
            }
        }

        this.fullPath = fullPath;

        this.name = null; // default if extraction fails
        if (fullPath) {
            var pathParts = fullPath.split("/");

            // Extract name from the end of the fullPath (account for trailing slash(es))
            while (!this.name && pathParts.length) {
                this.name = pathParts.pop();
            }
        }

        // TODO (issue #241)
        // http://www.w3.org/TR/2011/WD-file-system-api-20110419/#widl-Entry-filesystem
        this.filesystem = null;
    };

    NativeFileSystem.Entry.prototype.moveTo = function (parent, newName, successCallback, errorCallback) {
        // TODO (issue #241)
        // http://www.w3.org/TR/2011/WD-file-system-api-20110419/#widl-Entry-moveTo
    };

    NativeFileSystem.Entry.prototype.copyTo = function (parent, newName, successCallback, errorCallback) {
        // TODO (issue #241)
        // http://www.w3.org/TR/2011/WD-file-system-api-20110419/#widl-Entry-copyTo
    };

    NativeFileSystem.Entry.prototype.toURL = function (mimeType) {
        // TODO (issue #241)
        // http://www.w3.org/TR/2011/WD-file-system-api-20110419/#widl-Entry-toURL
    };

    NativeFileSystem.Entry.prototype.remove = function (successCallback, errorCallback) {
        // TODO (issue #241)
        // http://www.w3.org/TR/2011/WD-file-system-api-20110419/#widl-Entry-remove
    };

    NativeFileSystem.Entry.prototype.getParent = function (successCallback, errorCallback) {
        // TODO (issue #241)
        // http://www.w3.org/TR/2011/WD-file-system-api-20110419/#widl-Entry-remove
    };

    NativeFileSystem.Entry.prototype.getMetadata = function (successCallBack, errorCallback) {
        brackets.fs.stat(this.fullPath, function (err, stat) {
            if (err === brackets.fs.NO_ERROR) {
                var metadata = new NativeFileSystem.Metadata(stat.mtime);
                successCallBack(metadata);
            } else {
                errorCallback(NativeFileSystem._nativeToFileError(err));
            }
        });
    };

    /**
     * Stores information about a FileEntry
    */
    NativeFileSystem.Metadata = function (modificationTime) {
        // modificationTime is read only
        this.modificationTime = modificationTime;
    };

    /** class: FileEntry
     * This interface represents a file on a file system.
     *
     * @param {string} name
     * @constructor
     * @extends {Entry}
     */
    NativeFileSystem.FileEntry = function (name) {
        NativeFileSystem.Entry.call(this, name, false);
    };
    NativeFileSystem.FileEntry.prototype = new NativeFileSystem.Entry();

    /**
     * Creates a new FileWriter associated with the file that this FileEntry represents.
     *
     * @param {function (FileWriter)} successCallback
     * @param {function (FileError)} errorCallback
     */
    NativeFileSystem.FileEntry.prototype.createWriter = function (successCallback, errorCallback) {
        var fileEntry = this;

        // [NoInterfaceObject]
        // interface FileWriter : FileSaver
        var FileWriter = function (data) {
            NativeFileSystem.FileSaver.call(this, data);

            // FileWriter private memeber vars
            this._length = 0;
            this._position = 0;
        };

        FileWriter.prototype.length = function () {
            return this._length;
        };

        FileWriter.prototype.position = function () {
            return this._position;
        };

        // TODO (issue #241): handle Blob data instead of string
        FileWriter.prototype.write = function (data) {
            if (data === null || data === undefined) {
                throw new Error();
            }

            if (this.readyState === NativeFileSystem.FileSaver.WRITING) {
                throw new NativeFileSystem.FileException(NativeFileSystem.FileException.INVALID_STATE_ERR);
            }

            this._readyState = NativeFileSystem.FileSaver.WRITING;

            if (this.onwritestart) {
                // TODO (issue #241): progressevent
                this.onwritestart();
            }

            var self = this;

            brackets.fs.writeFile(fileEntry.fullPath, data, "utf8", function (err) {

                if ((err !== brackets.fs.NO_ERROR) && self.onerror) {
                    var fileError = NativeFileSystem._nativeToFileError(err);

                    // TODO (issue #241): set readonly FileSaver.error attribute
                    // self._error = fileError;
                    self.onerror(fileError);

                    // TODO (issue #241): partial write, update length and position
                }
                // else {
                    // TODO (issue #241): After changing data argument to Blob, use
                    // Blob.size to update position and length upon successful
                    // completion of a write.

                    // self.position = ;
                    // self.length = ;
                // }

                // DONE is set regardless of error
                self._readyState = NativeFileSystem.FileSaver.DONE;

                if (self.onwrite) {
                    // TODO (issue #241): progressevent
                    self.onwrite();
                }

                if (self.onwriteend) {
                    // TODO (issue #241): progressevent
                    self.onwriteend();
                }
            });
        };

        FileWriter.prototype.seek = function (offset) {
        };

        FileWriter.prototype.truncate = function (size) {
        };

        var fileWriter = new FileWriter();

        // initialize file length
        var result = new $.Deferred();
        brackets.fs.readFile(fileEntry.fullPath, "utf8", function (err, contents) {
            // Ignore "file not found" errors. It's okay if the file doesn't exist yet.
            if (err !== brackets.fs.ERR_NOT_FOUND) {
                fileWriter._err = err;
            }

            if (contents) {
                fileWriter._length = contents.length;
            }

            result.resolve();
        });

        result.done(function () {
            if (fileWriter._err && (errorCallback !== undefined)) {
                errorCallback(NativeFileSystem._nativeToFileError(fileWriter._err));
            } else if (successCallback !== undefined) {
                successCallback(fileWriter);
            }
        });
    };

    /**
     * Obtains the File objecte for a FileEntry object
     *
     * @param {function(...)} successCallback
     * @param {function(...)} errorCallback
     */
    NativeFileSystem.FileEntry.prototype.file = function (successCallback, errorCallback) {
        var newFile = new NativeFileSystem.File(this);
        successCallback(newFile);

        // TODO (issue #241): errorCallback
    };

    /**
     * This interface extends the FileException interface described in to add
     * several new error codes. Any errors that need to be reported synchronously,
     * including all that occur during use of the synchronous filesystem methods,
     * are reported using the FileException exception.
     *
     * @param {number} code The code attribute, on getting, must return one of the
     * constants of the FileException exception, which must be the most appropriate
     * code from the table below.
     */
    NativeFileSystem.FileException = function (code) {
        this.code = code || 0;
    };

    // FileException constants
    Object.defineProperties(
        NativeFileSystem.FileException,
        {
            NOT_FOUND_ERR:                { value: 1, writable: false },
            SECURITY_ERR:                 { value: 2, writable: false },
            ABORT_ERR:                    { value: 3, writable: false },
            NOT_READABLE_ERR:             { value: 4, writable: false },
            ENCODING_ERR:                 { value: 5, writable: false },
            NO_MODIFICATION_ALLOWED_ERR:  { value: 6, writable: false },
            INVALID_STATE_ERR:            { value: 7, writable: false },
            SYNTAX_ERR:                   { value: 8, writable: false },
            QUOTA_EXCEEDED_ERR:           { value: 10, writable: false }
        }
    );

    /**
     * This interface provides methods to monitor the asynchronous writing of blobs
     * to disk using progress events and event handler attributes.
     *
     * This interface is specified to be used within the context of the global
     * object (Window) and within Web Workers.
     *
     * @param {Blob} data
     * @constructor
     */
    NativeFileSystem.FileSaver = function (data) {
        // FileSaver private member vars
        this._data = data;
        this._readyState = NativeFileSystem.FileSaver.INIT;
        this._error = null;
    };

    // FileSaver constants
    Object.defineProperties(
        NativeFileSystem.FileSaver,
        {
            INIT:     { value: 1, writable: false },
            WRITING:  { value: 2, writable: false },
            DONE:     { value: 3, writable: false }
        }
    );

    // FileSaver methods

    /**
     *
     */
    NativeFileSystem.FileSaver.prototype.readyState = function () {
        return this._readyState;
    };

    // TODO (issue #241): http://dev.w3.org/2009/dap/file-system/file-writer.html#widl-FileSaver-abort-void
    NativeFileSystem.FileSaver.prototype.abort = function () {
        // If readyState is DONE or INIT, terminate this overall series of steps without doing anything else..
        if (this._readyState === NativeFileSystem.FileSaver.INIT || this._readyState === NativeFileSystem.FileSaver.DONE) {
            return;
        }

        // TODO (issue #241): Terminate any steps having to do with writing a file.

        // Set the error attribute to a FileError object with the code ABORT_ERR.
        this._error = new NativeFileSystem.FileError(FileError.ABORT_ERR);

        // Set readyState to DONE.
        this._readyState = NativeFileSystem.FileSaver.DONE;

        /*
        TODO (issue #241):
        Dispatch a progress event called abort
        Dispatch a progress event called writeend
        Stop dispatching any further progress events.
        Terminate this overall set of steps.
        */
    };

    /**
     * This interface represents a directory on a file system.
     *
     * @constructor
     * @param {string} name
     * @extends {Entry}
     */
    NativeFileSystem.DirectoryEntry = function (name) {
        NativeFileSystem.Entry.call(this, name, true);

        // TODO (issue #241): void removeRecursively (VoidCallback successCallback, optional ErrorCallback errorCallback);
    };
    NativeFileSystem.DirectoryEntry.prototype = new NativeFileSystem.Entry();

    NativeFileSystem.DirectoryEntry.prototype.getDirectory = function (path, options, successCallback, errorCallback) {
        // TODO (issue #241)
        // http://www.w3.org/TR/2011/WD-file-system-api-20110419/#widl-DirectoryEntry-getDirectory
    };

    NativeFileSystem.DirectoryEntry.prototype.removeRecursively = function (successCallback, errorCallback) {
        // TODO (issue #241)
        // http://www.w3.org/TR/2011/WD-file-system-api-20110419/#widl-DirectoryEntry-removeRecursively
    };

    NativeFileSystem.DirectoryEntry.prototype.createReader = function () {
        var dirReader = new NativeFileSystem.DirectoryReader();
        dirReader._directory = this;

        return dirReader;
    };

    /**
     * Creates or looks up a file.
     *
     * @param {string} path Either an absolute path or a relative path from this
     *        DirectoryEntry to the file to be looked up or created. It is an error
     *        to attempt to create a file whose immediate parent does not yet
     *        exist.
     * @param {Object.<string, boolean>} options
     * @param {function (number)} successCallback
     * @param {function (number)} errorCallback
     */
    NativeFileSystem.DirectoryEntry.prototype.getFile = function (path, options, successCallback, errorCallback) {
        var fileFullPath = path;

        // resolve relative paths relative to the DirectoryEntry
        // most absolute paths have a leading slash except Windows which has a drive letter followed by :/
        if (path.charAt(0) !== '/' && path.charAt(1) !== ":") {
            fileFullPath = this.fullPath + path;
        }

        var createFileEntry = function () {
            if (successCallback) {
                successCallback(new NativeFileSystem.FileEntry(fileFullPath));
            }
        };

        var createFileError = function (err) {
            if (errorCallback) {
                errorCallback(NativeFileSystem._nativeToFileError(err));
            }
        };

        // Use stat() to check if file exists
        brackets.fs.stat(fileFullPath, function (err, stats) {
            if ((err === brackets.fs.NO_ERROR)) {
                // NO_ERROR implies the path already exists

                // throw error if the file the path is a directory
                if (stats.isDirectory()) {
                    if (errorCallback) {
                        errorCallback(new NativeFileSystem.FileError(FileError.TYPE_MISMATCH_ERR));
                    }

                    return;
                }

                // throw error if the file exists but create is exclusive
                if (options.create && options.exclusive) {
                    if (errorCallback) {
                        errorCallback(new NativeFileSystem.FileError(FileError.PATH_EXISTS_ERR));
                    }

                    return;
                }

                // Create a file entry for the existing file. If create == true,
                // a file entry is created without error.
                createFileEntry();
            } else if (err === brackets.fs.ERR_NOT_FOUND) {
                // ERR_NOT_FOUND implies we write a new, empty file

                // create the file
                if (options.create) {
                    brackets.fs.writeFile(fileFullPath, "", "utf8", function (err) {
                        if (err) {
                            createFileError(err);
                        } else {
                            createFileEntry();
                        }
                    });

                    return;
                }

                // throw error if file not found and the create == false
                if (errorCallback) {
                    errorCallback(new NativeFileSystem.FileError(FileError.NOT_FOUND_ERR));
                }
            } else {
                // all other brackets.fs.stat() errors
                createFileError(err);
            }
        });
    };

    /** class: DirectoryReader
     */
    NativeFileSystem.DirectoryReader = function () {

    };

    /** readEntries
     *
     * @param {function(...)} successCallback
     * @param {function(...)} errorCallback
     * @returns {Array.<Entry>}
     */
    NativeFileSystem.DirectoryReader.prototype.readEntries = function (successCallback, errorCallback) {
        var rootPath = this._directory.fullPath;
        brackets.fs.readdir(rootPath, function (err, filelist) {
            if (!err) {
                var entries = [];
                var lastError = null;

                // stat() to determine type of each entry, then populare entries array with objects
                var masterPromise = Async.doInParallel(filelist, function (filename, index) {

                    var deferred = new $.Deferred();
                    var itemFullPath = rootPath + filelist[index];

                    brackets.fs.stat(itemFullPath, function (statErr, statData) {
                        if (!statErr) {
                            if (statData.isDirectory()) {
                                entries[index] = new NativeFileSystem.DirectoryEntry(itemFullPath);
                            } else if (statData.isFile()) {
                                entries[index] = new NativeFileSystem.FileEntry(itemFullPath);
                            } else {
                                entries[index] = null;  // neither a file nor a dir, so don't include it
                            }
                            deferred.resolve();
                        } else {
                            lastError = NativeFileSystem._nativeToFileError(statErr);
                            deferred.reject(lastError);
                        }
                    });

                    return deferred;
                }, true);

                // We want the error callback to get called after some timeout (in case some deferreds don't return).
                // So, we need to wrap masterPromise in another deferred that has this timeout functionality
                var timeoutWrapper = Async.withTimeout(masterPromise, NativeFileSystem.ASYNC_TIMEOUT);

                // Add the callbacks to this top-level Promise, which wraps all the individual deferred objects
                timeoutWrapper.then(
                    function () { // success
                        // The entries array may have null values if stat returned things that were
                        // neither a file nor a dir. So, we need to clean those out.
                        var cleanedEntries = [], i;
                        for (i = 0; i < entries.length; i++) {
                            if (entries[i]) {
                                cleanedEntries.push(entries[i]);
                            }
                        }
                        successCallback(cleanedEntries);
                    },
                    function (err) { // error
                        if (err === Async.ERROR_TIMEOUT) {
                            // SECURITY_ERR is the HTML5 File catch-all error, and there isn't anything
                            // more fitting for a timeout.
                            err = new NativeFileSystem.FileError(FileError.SECURITY_ERR);
                        } else {
                            err = lastError;
                        }

                        if (errorCallback) {
                            errorCallback(err);
                        }
                    }
                );

            } else { // There was an error reading the initial directory.
                errorCallback(NativeFileSystem._nativeToFileError(err));
            }
        });
    };

    /** class: FileReader
     *
     * @extends {EventTarget}
     */
    NativeFileSystem.FileReader = function () {
        // TODO (issue #241): this classes should extend EventTarget

        // states
        this.EMPTY = 0;
        this.LOADING = 1;
        this.DONE = 2;

        // readyState is read only
        this.readyState = this.EMPTY;

        // File or Blob data
        // TODO (issue #241): readonly attribute any result;
        // TODO (issue #241): readonly attribute DOMError error;

        // event handler attributes
        this.onloadstart = null;
        this.onprogress = null;
        this.onload = null;
        this.onabort = null;
        this.onerror = null;
        this.onloadend = null;
    };
    // TODO (issue #241): extend EventTarget (draft status, not implememnted in webkit)
    // NativeFileSystem.FileReader.prototype = new NativeFileSystem.EventTarget()

    NativeFileSystem.FileReader.prototype.readAsArrayBuffer = function (blob) {
        // TODO (issue #241): implement
        // http://www.w3.org/TR/2011/WD-FileAPI-20111020/#dfn-readAsArrayBuffer
    };

    NativeFileSystem.FileReader.prototype.readAsBinaryString = function (blob) {
        // TODO (issue #241): implement
        // http://www.w3.org/TR/2011/WD-FileAPI-20111020/#dfn-readAsBinaryStringAsync
    };

    NativeFileSystem.FileReader.prototype.readAsDataURL = function (blob) {
        // TODO (issue #241): implement
        // http://www.w3.org/TR/2011/WD-FileAPI-20111020/#dfn-readAsDataURL
    };

    NativeFileSystem.FileReader.prototype.abort = function () {
        // TODO (issue #241): implement
        // http://www.w3.org/TR/2011/WD-FileAPI-20111020/#dfn-abort
    };

    /** readAsText
     *
     * @param {Blob} blob
     * @param {string} encoding
     */
    NativeFileSystem.FileReader.prototype.readAsText = function (blob, encoding) {
        var self = this;

        if (!encoding) {
            encoding = "utf-8";
        }

        if (this.readyState === this.LOADING) {
            throw new InvalidateStateError();
        }

        this.readyState = this.LOADING;

        if (this.onloadstart) {
            this.onloadstart(); // TODO (issue #241): progressevent
        }

        brackets.fs.readFile(blob._fullPath, encoding, function (err, data) {

            // TODO (issue #241): the event objects passed to these event handlers is fake and incomplete right now
            var fakeEvent = {
                loaded: 0,
                total: 0
            };

            // The target for this event is the FileReader and the data/err result is stored in the FileReader
            fakeEvent.target = self;
            self.result = data;
            self.error = NativeFileSystem._nativeToFileError(err);

            if (err) {
                self.readyState = self.DONE;
                if (self.onerror) {
                    self.onerror(fakeEvent);
                }
            } else {
                self.readyState = self.DONE;

                // TODO (issue #241): this should be the file/blob size, but we don't have code to get that yet, so for know assume a file size of 1
                // and since we read the file in one go, assume 100% after the first read
                fakeEvent.loaded = 1;
                fakeEvent.total = 1;

                if (self.onprogress) {
                    self.onprogress(fakeEvent);
                }

                // TODO (issue #241): onabort not currently supported since our native implementation doesn't support it
                // if (self.onabort)
                //    self.onabort(fakeEvent);

                if (self.onload) {
                    self.onload(fakeEvent);
                }

                if (self.onloadend) {
                    self.onloadend();
                }
            }

        });
    };

    /** class: Blob
     *
     * @constructor
     * param {Entry} entry
     */
    NativeFileSystem.Blob = function (fullPath) {
        this._fullPath = fullPath;

        // TODO (issue #241): implement, readonly
        this.size = 0;

        // TODO (issue #241): implement, readonly
        this.type = null;
    };

    NativeFileSystem.Blob.prototype.slice = function (start, end, contentType) {
        // TODO (issue #241): implement
        // http://www.w3.org/TR/2011/WD-FileAPI-20111020/#dfn-slice
    };

    /** class: File
     *
     * @constructor
     * param {Entry} entry
     * @extends {Blob}
     */
    NativeFileSystem.File = function (entry) {
        NativeFileSystem.Blob.call(this, entry.fullPath);

        // TODO (issue #241): implement, readonly
        this.name = "";

        // TODO (issue #241): implement, readonly
        this.lastModifiedDate = null;
    };

    /** class: FileError
     *
     * Implementation of HTML file API error code return class. Note that we don't
     * actually define the error codes here--we rely on the browser's built-in FileError
     * class's constants. In other words, external clients of this API should always
     * use FileError.<constant-name>, not NativeFileSystem.FileError.<constant-name>.
     *
     * @constructor
     * @param {number} code The error code to return with this FileError. Must be
     * one of the codes defined in the FileError class.
     */
    NativeFileSystem.FileError = function (code) {
        this.code = code || 0;
    };

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


keep keep keep keep replace replace keep keep keep

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

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

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

    //constants
    var TAG_NAME = "tagName",
        ATTR_NAME = "attr.name",
        ATTR_VALUE = "att.value";

    /**
     * @private
     * moves the current context backwards by one token
     * @param {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}} ctx
     * @return {boolean} whether the context changed
     */
    function _movePrevToken(ctx) {
        if (ctx.pos.ch <= 0 || ctx.token.start <= 0) {
            //move up a line
            if (ctx.pos.line <= 0) {
                return false; //at the top already
            }
            ctx.pos.line--;
            ctx.pos.ch = ctx.editor.getLine(ctx.pos.line).length;
        } else {
            ctx.pos.ch = ctx.token.start;
        }
        ctx.token = ctx.editor.getTokenAt(ctx.pos);
        return true;
    }

    /**
     * @private
     * moves the current context forward by one token
     * @param {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}} ctx
     * @return {boolean} whether the context changed
     */
    function _moveNextToken(ctx) {
        var eol = ctx.editor.getLine(ctx.pos.line).length;
        if (ctx.pos.ch >= eol || ctx.token.end >= eol) {
            //move down a line
            if (ctx.pos.line === ctx.editor.lineCount()) {
                return false; //at the bottom
            }
            ctx.pos.line++;
            ctx.pos.ch = 0;
        } else {
            ctx.pos.ch = ctx.token.end + 1;
        }
        ctx.token = ctx.editor.getTokenAt(ctx.pos);
        return true;
    }

   /**
     * @private
     * creates a context object
     * @param {CodeMirror} editor
     * @param {{ch:{string}, line:{number}} pos
     * @return {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}}
     */
    function _getInitialContext(editor, pos) {
        return {
            "editor": editor,
            "pos": pos,
            "token": editor.getTokenAt(pos)
        };
    }

    /**
     * @private
     * in the given context, get the character offset of pos from the start of the token
     * @param {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}} context
     * @return {number}
     */
    function _offsetInToken(ctx) {
        var offset = ctx.pos.ch - ctx.token.start;
        if (offset < 0) {
            console.log("CodeHintUtils: _offsetInToken - Invalid context: the pos what not in the current token!");
        }
        return offset;
    }

   /**
     * @private
     * Sometimes as attr values are getting typed, if the quotes aren't balanced yet
     * some extra 'non attribute value' text gets included in the token. This attempts
     * to assure the attribute value we grab is always good
     * @param {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}} context
     * @return { val:{string}, offset{number}}
     */
    function _extractAttrVal(ctx) {
        var attrValue = ctx.token.string;
        var startChar = attrValue.charAt(0);
        var endChar = attrValue.charAt(attrValue.length - 1);
        var offset = _offsetInToken(ctx);

        //If this is a fully quoted value, return the whole
        //thing regardless of position
        if (attrValue.length > 1 &&
                (startChar === "'" || startChar === '"') &&
                endChar === startChar) {
            //strip the quotes and return;
            attrValue = attrValue.substring(1, attrValue.length - 1);
            offset = offset - 1 > attrValue.length ? attrValue.length : offset - 1;
            return {val: attrValue, offset: offset};
        }

        //The att value it getting edit in progress. There is possible extra
        //stuff in this token state since the quote isn't closed, so we assume
        //the stuff from the quote to the current pos is definitely in the attribute
        //value.
        if (offset > 0) {
            attrValue = attrValue.substring(0, offset);
        }

        //If the attrValue start with a quote, trim that now
        startChar = attrValue.charAt(0);
        if (startChar === "'" || startChar === '"') {
            attrValue = attrValue.substring(1);
            offset--;
        }

        return {val: attrValue, offset: offset};
    }

      /**
     * @private
     * Gets the tagname from where ever you are in the currect state
     * @param {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}} context
     * @return {string}
     */
    function _extractTagName(ctx) {
        if (ctx.token.state.tagName) {
            return ctx.token.state.tagName; //XML mode
        } else {
            return ctx.token.state.htmlState.tagName; //HTML mode
        }
    }

    /**
     * Creates a tagInfo object and assures all the values are entered or are empty strings
     * @param {string} token what is getting edited
     * @param {string} tagName The name of the tag
     * @param {string} attrName The name of the attribute
     * @param {string} attrValue The value of the attribute
     * @return {{tagName:string, attr{name:string, value:string}} A tagInfo object with some context
     *              about the current tag hint.
     */
    function createTagInfo(token, offset, tagName, attrName, attrValue) {
        return { tagName: tagName || "",
                 attr:
                    { name: attrName || "",
                      value: attrValue || ""},
                 editing:
                    { token: token || "",
                      offsetInToken: offset || 0} };
    }

    function _getTagInfoStartingFromAttrValue(ctx) {
        // Assume we in the attr value
        // and validate that by going backwards
        var attrInfo = _extractAttrVal(ctx);
        var attrVal = attrInfo.val;
        var offset = attrInfo.offset;

        //Move to the prev token, and check if it's "="
        if (!_movePrevToken(ctx) || ctx.token.string !== "=") {
            return createTagInfo();
        }

        //Move to the prev token, and check if it's an attribute
        if (!_movePrevToken(ctx) || ctx.token.className !== "attribute") {
            return createTagInfo();
        }

        var attrName = ctx.token.string;
        var tagName = _extractTagName(ctx);

        //We're good.
        return createTagInfo(ATTR_VALUE, offset, tagName, attrName, attrVal);
    }

    function _getTagInfoStartingFromAttrName(ctx) {
        //Verify We're in the attribute name, move forward and try to extract the rest of
        //the info. If the user it typing the attr the rest might not be here
        if (ctx.token.className !== "attribute") {
            return createTagInfo();
        }

        var tagName = _extractTagName(ctx);
        var attrName = ctx.token.string;
        var offset = _offsetInToken(ctx);

        if (!_moveNextToken(ctx) || ctx.token.string !== "=") {
            return createTagInfo(ATTR_NAME, offset, tagName, attrName);
        }

        if (!_moveNextToken(ctx)) {
            //The user just typed "=" so they are about to add the attr val
            return createTagInfo(ATTR_NAME, offset, tagName, attrName);
        }
        //this should be the attrvalue
        var attrInfo = _extractAttrVal(ctx);
        var attrVal = attrInfo.val;

        return createTagInfo(ATTR_NAME, offset, tagName, attrName, attrVal);
    }

    /**
     * Figure out if we're in a tag, and if we are
     * An example token stream for this tag is  :
     *      className:tag       string:""
     * @param {CodeMirror} editor An instance of a CodeMirror editor
     * @param {{ch: number, line: number}} pos  A CM pos (likely from editor.getCursor())
     * @return {{tagName:string, attr{name:string, value:string}} A tagInfo object with some context
     *              about the current tag hint.
     */
    function getTagInfo(editor, pos) {
        var ctx = _getInitialContext(editor, pos),
            offset = _offsetInToken(ctx),
            tagInfo,
            editingToken;

        //check and see where we are in the tag
        //first check, if we're in an all whitespace token and move back
        //and see what's before us
        if (ctx.token.string.length > 0 && ctx.token.string.trim().length === 0) {
            if (!_movePrevToken(ctx)) {
                return createTagInfo();
            }

            if (ctx.token.className !== "tag") {
                //if wasn't the tag name, assume it was an attr value
                tagInfo = _getTagInfoStartingFromAttrValue(ctx);
                //We don't want to give context for the previous attr
                //and we want it to look like the user is going to add a new attr
                if (tagInfo.tagName) {
                    return createTagInfo(ATTR_NAME, 0, tagInfo.tagName);
                }
                return createTagInfo();
            }

            //we know the tag was here, so they user is adding an attr name
            editingToken = ATTR_NAME;
            offset = 0;
        }

        if (ctx.token.className === "tag") {
            //check to see if this is the closing of a tag (either the start or end)
            if (ctx.token.string === ">") {
                return createTagInfo();
            }

            if (!editingToken) {
                editingToken = TAG_NAME;
                offset--; //need to take off 1 for the leading "<"
            }

            //we're actually in the tag, just return that as we have no relevant
            //info about what attr is selected
            return createTagInfo(editingToken, offset, _extractTagName(ctx));
        }

        if (ctx.token.string === "=") {
            //we could be between the attr and the value
            //step back and check
            if (!_movePrevToken(ctx) || ctx.token.className !== "attribute") {
                return createTagInfo();
            }

            //This the the = as the are going to add an value now
            editingToken = ATTR_VALUE;
            offset = 0;
        }

        if (ctx.token.className === "attribute") {
            tagInfo = _getTagInfoStartingFromAttrName(ctx);
        } else {
            // if we're not at a tag, "=", or attribute name, assume we're in the value
            tagInfo = _getTagInfoStartingFromAttrValue(ctx);
        }

        if (editingToken && tagInfo.tagName) {
            tagInfo.editing.token = editingToken;
            tagInfo.editing.offsetInToken = offset;
        }

        return tagInfo;
    }

    // Define public API
    exports.TAG_NAME = TAG_NAME;
    exports.ATTR_NAME = ATTR_NAME;
    exports.ATTR_VALUE = ATTR_VALUE;

    exports.getTagInfo = getTagInfo;
    //The createTagInfo is really only for the unit tests so they can make the same structure to
    //compare results with
    exports.createTagInfo = createTagInfo;
});


module Jasmine
  module Core
    VERSION = "1.1.0"
  end
end


/* Reset.less
 * Props to Eric Meyer (meyerweb.com) for his CSS reset file. We're using an adapted version here	that cuts out some of the reset HTML elements we will never need here (i.e., dfn, samp, etc).
 * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */

// ERIC MEYER RESET
// --------------------------------------------------

html, body { margin: 0; padding: 0; }
h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, cite, code, del, dfn, em, img, q, s, samp, small, strike, strong, sub, sup, tt, var, dd, dl, dt, li, ol, ul, fieldset, form, label, legend, button, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; font-weight: normal; font-style: normal; font-size: 100%; line-height: 1; font-family: inherit; }
table { border-collapse: collapse; border-spacing: 0; }
ol, ul { list-style: none; }
q:before, q:after, blockquote:before, blockquote:after { content: ""; }

// Normalize.css
// Pulling in select resets form the normalize.css project
// --------------------------------------------------

// Display in IE6-9 and FF3
// -------------------------
// Source: http://github.com/necolas/normalize.css
html {
  overflow-y: scroll;
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
      -ms-text-size-adjust: 100%;
}
// Focus states
a:focus {
  outline: thin dotted;
}
// Hover & Active
a:hover,
a:active {
  outline: 0;
}

// Display in IE6-9 and FF3
// -------------------------
// Source: http://github.com/necolas/normalize.css
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
nav,
section {
  display: block;
}

// Display block in IE6-9 and FF3
// -------------------------
// Source: http://github.com/necolas/normalize.css
audio,
canvas,
video {
  display: inline-block;
  *display: inline;
  *zoom: 1;
}

// Prevents modern browsers from displaying 'audio' without controls
// -------------------------
// Source: http://github.com/necolas/normalize.css
audio:not([controls]) {
    display: none;
}

// Prevents sub and sup affecting line-height in all browsers
// -------------------------
// Source: http://github.com/necolas/normalize.css
sub,
sup {
  font-size: 75%;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}
sup {
  top: -0.5em;
}
sub {
  bottom: -0.25em;
}

// Img border in a's and image quality
// -------------------------
// Source: http://github.com/necolas/normalize.css
img {
    border: 0;
    -ms-interpolation-mode: bicubic;
}

// Forms
// -------------------------
// Source: http://github.com/necolas/normalize.css

// Font size in all browsers, margin changes, misc consistency
button,
input,
select,
textarea {
  font-size: 100%;
  margin: 0;
  vertical-align: baseline;
  *vertical-align: middle;
}
button,
input {
  line-height: normal; // FF3/4 have !important on line-height in UA stylesheet
  *overflow: visible; // Inner spacing ie IE6/7
}
button::-moz-focus-inner,
input::-moz-focus-inner { // Inner padding and border oddities in FF3/4
  border: 0;
  padding: 0;
}
button,
input[type="button"],
input[type="reset"],
input[type="submit"] {
  cursor: pointer; // Cursors on all buttons applied consistently
  -webkit-appearance: button; // Style clicable inputs in iOS
}
input[type="search"] { // Appearance in Safari/Chrome
  -webkit-appearance: textfield;
  -webkit-box-sizing: content-box;
     -moz-box-sizing: content-box;
          box-sizing: content-box;
}
input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5
}
textarea {
  overflow: auto; // Remove vertical scrollbar in IE6-9
  vertical-align: top; // Readability and alignment cross-browser
}


//we're actually in the tag, just return that as we have no relevant

return createTagInfo(editingToken, offset, _extractTagName(ctx));

//we could be between the attr and the value

keep keep keep replace keep keep keep keep

return createTagInfo(hint, offset, _extractTagName(ctx));


Bootstrap v1.4.0
https://github.com/twitter/bootstrap/zipball/v1.4.0
http://twitter.github.com/bootstrap/


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

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

 /**
  * This is JavaScript API exposed to the native shell when Brackets is run in a native shell rather than a browser.
  */
define(function (require, exports, module) {
    'use strict';

    // Load dependent modules
    var CommandManager     = require("CommandManager");

    /**
     * The native function BracketsShellAPI::DispatchBracketsJSCommand calls this function in order to enable
     * calling Brackets commands from the native shell.
     */
    function executeCommand(eventName) {
        var evt = document.createEvent("Event");
        evt.initEvent(eventName, false, true);

        CommandManager.execute(eventName, {evt: evt});

        //return if default was prevented
        return evt.defaultPrevented;
    }

    exports.executeCommand = executeCommand;
});


html {
    color: "red";
}

HTML {
    color: "orange";
}

.firstGrade {
    color: "green";
}

.FIRSTGRADE {
    color: "yellow";
}

#brack3ts {
    color: "blue";
}

#BRACK3TS {
    color: "black";
}


//This the the = as the are going to add an value now

if (ctx.token.className === "attribute") {

keep keep keep keep replace keep keep keep keep


/*
 * jsTree default theme 1.0
 * Supported features: dots/no-dots, icons/no-icons, focused, loading
 * Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search
 */

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

.jstree-default .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 2px 0 1px; }
.jstree-default .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 2px 0 1px; }
.jstree-default a .jstree-icon { background-position:-56px -19px; }
.jstree-default a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; }

.jstree-default.jstree-focused { background:#ffffee; }

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

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

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

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

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

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

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

/* IE6 BEGIN */
.jstree-default li,
.jstree-default ins,
#vakata-dragged.jstree-default .jstree-invalid,
#vakata-dragged.jstree-default .jstree-ok,
#jstree-marker.jstree-default { _background-image:url("d.gif"); }
.jstree-default .jstree-open ins { _background-position:-72px 0; }
.jstree-default .jstree-closed ins { _background-position:-54px 0; }
.jstree-default .jstree-leaf ins { _background-position:-36px 0; }
.jstree-default a ins.jstree-icon { _background-position:-56px -19px; }
#vakata-contextmenu.jstree-default-context ins { _display:none; }
#vakata-contextmenu.jstree-default-context li { _zoom:1; }
.jstree-default .jstree-undetermined a .jstree-checkbox { _background-position:-20px -19px; }
.jstree-default .jstree-checked a .jstree-checkbox { _background-position:-38px -19px; }
.jstree-default .jstree-unchecked a .jstree-checkbox { _background-position:-2px -19px; }
/* IE6 END */


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

echo $BASEDIR

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


// if we're not at a tag, "=", or attribute name, assume we're in the value

keep keep keep keep keep replace replace replace keep keep keep


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

!function( $ ){

  "use strict"

  /* DROPDOWN PLUGIN DEFINITION
   * ========================== */

  $.fn.dropdown = function ( selector ) {
    return this.each(function () {
      $(this).delegate(selector || d, 'click', function (e) {
        var li = $(this).parent('li')
          , isActive = li.hasClass('open')

        clearMenus()
        !isActive && li.toggleClass('open')
        return false
      })
    })
  }

  /* APPLY TO STANDARD DROPDOWN ELEMENTS
   * =================================== */

  var d = 'a.menu, .dropdown-toggle'

  function clearMenus() {
    $(d).parent('li').removeClass('open')
  }

  $(function () {
    $('html').bind("click", clearMenus)
    $('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' )
  })

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


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

// TOPBAR
// ------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  }
}

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

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

// WELLS
// -----

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

// MODALS
// ------

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

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

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

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

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

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

// TWIPSY
// ------

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

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

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

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

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

// LABELS
// ------

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

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

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


// (this event triggers EditorManager to actually switch editors in the UI)

// Set the language layer in the preferences.

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

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

/*global define, $, localStorage, brackets */
/*unittests: Preferences Manager */

/**
 * PreferencesManager
 *
 */
define(function (require, exports, module) {
    "use strict";

    var OldPreferenceStorage = require("preferences/PreferenceStorage").PreferenceStorage,
        FileUtils         = require("file/FileUtils"),
        ExtensionLoader   = require("utils/ExtensionLoader"),
        PreferencesBase   = require("preferences/PreferencesBase"),
        _                 = require("thirdparty/lodash");

    /**
     * The local storage ID
     * @const
     * @type {string}
     */
    var PREFERENCES_CLIENT_ID = "com.adobe.brackets.preferences";

    /**
     * The prefix used in the generated client ID
     * @const
     * @type {string}
     */
    var CLIENT_ID_PREFIX = "com.adobe.brackets.";

    // Private Properties
    var preferencesKey,
        prefStorage,
        persistentStorage,
        extensionPaths,
        doLoadPreferences   = false;

    /**
     * @private
     * Returns an array with the extension paths used in Brackets. The result is stored on a
     * private variable on the first call and used to return the value on the next calls.
     * @return {Array.<string>}
     */
    function _getExtensionPaths() {
        if (!extensionPaths) {
            var dirPath = FileUtils.getNativeBracketsDirectoryPath();

            extensionPaths = [
                dirPath + "/extensions/default/",
                dirPath + "/extensions/dev/",
                ExtensionLoader.getUserExtensionPath() + "/"
            ];
        }
        return extensionPaths;
    }

    /**
     * This method returns a standardized ClientID for a given requireJS module object
     * @param {!{id: string, uri: string}} module - A requireJS module object
     * @return {string} The ClientID
     */
    function getClientID(module) {
        var paths = exports._getExtensionPaths();
        var pathExp, pathUrl, clientID;

        paths.some(function (path) {
            if (module.uri.toLocaleLowerCase().indexOf(path.toLocaleLowerCase()) === 0) {
                pathUrl = path;
                return true;
            }
        });

        if (pathUrl) {
            clientID = CLIENT_ID_PREFIX + module.uri.replace(pathUrl, "");
        } else {
            clientID = CLIENT_ID_PREFIX + module.id;
        }
        return clientID;
    }

    /**
     * Retreive the preferences data for the given clientID.
     * @param {string|{id: string, uri: string}} clientID - A unique identifier or a requireJS module object
     * @param {string} defaults - Default preferences stored as JSON
     * @return {PreferenceStorage}
     */
    function getPreferenceStorage(clientID, defaults) {
        if (!clientID || (typeof clientID === "object" && (!clientID.id || !clientID.uri))) {
            console.error("Invalid clientID");
            return;
        }
        if (typeof clientID === "object") {
            clientID = getClientID(clientID);
        }

        var prefs = prefStorage[clientID];

        if (prefs === undefined) {
            // create a new empty preferences object
            prefs = (defaults && JSON.stringify(defaults)) ? defaults : {};
            prefStorage[clientID] = prefs;
        } else if (defaults) {
            // add new defaults
            _.forEach(defaults, function (value, key) {
                if (prefs[key] === undefined) {
                    prefs[key] = value;
                }
            });
        }

        return new OldPreferenceStorage(clientID, prefs);
    }

    /**
     * Save all preference clients.
     */
    function savePreferences() {
        // save all preferences
        persistentStorage.setItem(preferencesKey, JSON.stringify(prefStorage));
    }

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

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

    /**
     * @private
     * Initialize persistent storage implementation
     */
    function _initStorage(storage) {
        persistentStorage = storage;

        if (doLoadPreferences) {
            prefStorage = JSON.parse(persistentStorage.getItem(preferencesKey));
        }

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

    // Check localStorage for a preferencesKey. Production and unit test keys
    // are used to keep preferences separate within the same storage implementation.
    preferencesKey = localStorage.getItem("preferencesKey");

    if (!preferencesKey) {
        // use default key if none is found
        preferencesKey = PREFERENCES_CLIENT_ID;
        doLoadPreferences = true;
    } else {
        // using a non-default key, check for additional settings
        doLoadPreferences = !!(localStorage.getItem("doLoadPreferences"));
    }

    // Use localStorage by default
    _initStorage(localStorage);

    // Public API
    exports.getPreferenceStorage    = getPreferenceStorage;
    exports.savePreferences         = savePreferences;
    exports.getClientID             = getClientID;

    // Unit test use only
    exports._reset                  = _reset;
    exports._getExtensionPaths      = _getExtensionPaths;

    // New code follows. The code above (with the exception of the imports) is
    // deprecated.

    // The SETTINGS_FILENAME is used with a preceding "." within user projects
    var SETTINGS_FILENAME = "brackets.prefs",
        STATE_FILENAME    = "state.json";

    var preferencesManager = new PreferencesBase.PreferencesManager();

    // User-level preferences
    var userPrefFile = brackets.app.getApplicationSupportDirectory() + "/" + SETTINGS_FILENAME;

    preferencesManager.addScope("user", new PreferencesBase.FileStorage(userPrefFile, true));

    // Session-level preferences
    preferencesManager.addScope("session", new PreferencesBase.MemoryStorage());

    // Set up the layers
    var languageLayer = new PreferencesBase.LanguageLayer();
    preferencesManager.addLayer("language", languageLayer);

    var pathLayer = new PreferencesBase.PathLayer();
    preferencesManager.addLayer("path", pathLayer);

    // "State" is stored like preferences but it is not generally intended to be user-editable.
    // It's for more internal, implicit things like window size, working set, etc.
    var stateManager = new PreferencesBase.PreferencesManager();
    var userStateFile = brackets.app.getApplicationSupportDirectory() + "/" + SETTINGS_FILENAME;

    stateManager.addScope("user", new PreferencesBase.FileStorage(userStateFile, true));

    // Convenience function that sets a preference and then saves the file, mimicking the
    // old behavior a bit more closely.
    function setValueAndSave(scopeName, id, value) {
        preferencesManager.set(scopeName, id, value);
        preferencesManager.save();
    }

    // Private API for unit testing and use elsewhere in Brackets core
    exports._manager = preferencesManager;
    exports._setLanguage = languageLayer.setLanguage.bind(languageLayer);
    exports._setCurrentEditingFile = pathLayer.setFilename.bind(pathLayer);

    // Public API

    // PreferencesManager change messages are sent out as preferenceChange messages
    // from this module.
    $(preferencesManager).on("change", function (e, data) {
        $(exports).trigger("preferenceChange", data);
    });

    exports.get = preferencesManager.get.bind(preferencesManager);
    exports.set = preferencesManager.set.bind(preferencesManager);
    exports.save = preferencesManager.save.bind(preferencesManager);
    exports.getPreference = preferencesManager.getPreference.bind(preferencesManager);
    exports.setValueAndSave = setValueAndSave;
    exports.addScope = preferencesManager.addScope.bind(preferencesManager);
    exports.stateManager = stateManager;
    exports.FileStorage = PreferencesBase.FileStorage;
    exports.SETTINGS_FILENAME = SETTINGS_FILENAME;
    exports.definePreference = preferencesManager.definePreference.bind(preferencesManager);
});


var assert = require('assert');
var traverse = require('traverse');

exports.stop = function () {
    var visits = 0;
    traverse('abcdefghij'.split('')).forEach(function (node) {
        if (typeof node === 'string') {
            visits ++;
            if (node === 'e') this.stop()
        }
    });

    assert.equal(visits, 5);
};

exports.stopMap = function () {
    var s = traverse('abcdefghij'.split('')).map(function (node) {
        if (typeof node === 'string') {
            if (node === 'e') this.stop()
            return node.toUpperCase();
        }
    }).join('');

    assert.equal(s, 'ABCDEfghij');
};

exports.stopReduce = function () {
    var obj = {
        a : [ 4, 5 ],
        b : [ 6, [ 7, 8, 9 ] ]
    };
    var xs = traverse(obj).reduce(function (acc, node) {
        if (this.isLeaf) {
            if (node === 7) this.stop();
            else acc.push(node)
        }
        return acc;
    }, []);

    assert.deepEqual(xs, [ 4, 5, 6 ]);
};


/*
 * 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 */
define(function (require, exports, module) {
    'use strict';

    // Each suite or spec must have this.category === "performance" to be filtered properly
    require("perf/Performance-test");
});


<!doctype html>

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

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

jQuery UI Core Test Suite

<map name="mymap">
	<area shape="rect" coords="1,1,2,2" href="foo.html" id="areaCoordsHref" alt="">
	<area href="foo.html" id="areaNoCoordsHref" alt="">
</map>
<map name="mymap2">
	<area shape="rect" coords="1,1,2,2" href="foo.html" id="areaNoImg" alt="">
</map>

<form id="formNoTabindex"></form>
<form id="formTabindex" tabindex="1"></form>

	<input id="visibleAncestor-inputTypeNone">
	<input type="text" id="visibleAncestor-inputTypeText">
	<input type="checkbox" id="visibleAncestor-inputTypeCheckbox">
	<input type="radio" id="visibleAncestor-inputTypeRadio">
	<input type="button" id="visibleAncestor-inputTypeButton" value="visibleAncestor-inputTypeButton">
	<input type="hidden" id="visibleAncestor-inputTypeHidden">
	<button id="visibleAncestor-button">x</button>
	<select id="visibleAncestor-select">
		<option>option</option>
	</select>
	<textarea id="visibleAncestor-textarea">x</textarea>
	<object id="visibleAncestor-object" codebase="about:blank">xxx</object>
	anchor
	anchor
	x

x

	x

x

	<input id="disabledElement-inputTypeNone" disabled="disabled">
	<input type="text" id="disabledElement-inputTypeText" disabled="disabled">
	<input type="checkbox" id="disabledElement-inputTypeCheckbox" disabled="disabled">
	<input type="radio" id="disabledElement-inputTypeRadio" disabled="disabled">
	<input type="button" id="disabledElement-inputTypeButton" disabled="disabled" value="disabledElement-inputTypeButton">
	<input type="hidden" id="disabledElement-inputTypeHidden" disabled="disabled">
	<button id="disabledElement-button" disabled="disabled"></button>
	<select id="disabledElement-select" disabled="disabled"></select>
	<textarea id="disabledElement-textarea" disabled="disabled"></textarea>

		<input id="displayNoneAncestor-input">
		.

		<input id="visibilityHiddenAncestor-input">
		.

	.
	.

	<input id="displayNone-input" style="display: none;">
	<input id="visibilityHidden-input" style="visibility: hidden;">

	<input id="inputTabindex0" tabindex="0">
	<input id="inputTabindex10" tabindex="10">
	<input id="inputTabindex-1" tabindex="-1">
	<input id="inputTabindex-50" tabindex="-50">

	.
	.
	.
	.

.

.

.


var languageLayer = new PreferencesBase.LanguageLayer();

var pathLayer = new PreferencesBase.PathLayer();

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

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

var stateManager = new PreferencesBase.PreferencesManager();

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

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


// Safe toJSON
module.exports =
function getSafe (self, uuid) {
  if (typeof self === 'object' || typeof self === 'function') var safe = {}
  if (Array.isArray(self)) var safe = []

  var recurse = []

  Object.defineProperty(self, uuid, {})

  var attrs = Object.keys(self).filter(function (i) {
    if (i === uuid) return false
    if ( (typeof self[i] !== 'object' && typeof self[i] !== 'function') || self[i] === null) return true
    return !(Object.getOwnPropertyDescriptor(self[i], uuid))
  })

  for (var i=0;i<attrs.length;i++) {
    if ( (typeof self[attrs[i]] !== 'object' && typeof self[attrs[i]] !== 'function') ||
          self[attrs[i]] === null
        ) {
      safe[attrs[i]] = self[attrs[i]]
    } else {
      recurse.push(attrs[i])
      Object.defineProperty(self[attrs[i]], uuid, {})
    }
  }

  for (var i=0;i<recurse.length;i++) {
    safe[recurse[i]] = getSafe(self[recurse[i]], uuid)
  }

  return safe
}