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

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

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

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

    describe("NativeFileSystem", function () {

        var _err;

        beforeEach(function () {
            this.path = SpecRunnerUtils.getTestPath("/spec/NativeFileSystem-test-files");
            this.file1content = "Here is file1\n";
        });

        describe("Reading a directory", function () {

            beforeEach(function () {
                this.addMatchers({
                    toContainDirectoryWithName: function (expected) {
                        var i;
                        for (i = 0; i < this.actual.length; ++i) {
                            if (this.actual[i].isDirectory && this.actual[i].name === expected) {
                                return true;
                            }
                        }
                        return false;
                    },
                    toContainFileWithName: function (expected) {
                        var i;
                        for (i = 0; i < this.actual.length; ++i) {
                            if (this.actual[i].isFile && this.actual[i].name === expected) {
                                return true;
                            }
                        }
                        return false;
                    }
                });
            });

            it("should read a directory from disk", function () {
                var entries = null;
                var readComplete = false, gotError = false;

                function requestNativeFileSystemSuccessCB(nfs) {
                    var reader = nfs.createReader();

                    var successCallback = function (e) { entries = e; readComplete = true; };
                    var errorCallback = function () { readComplete = true; gotError = true; };

                    reader.readEntries(successCallback, errorCallback);
                }

                var nfs = NativeFileSystem.requestNativeFileSystem(this.path, requestNativeFileSystemSuccessCB);

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

                runs(function () {
                    expect(gotError).toBe(false);
                    expect(entries).toContainDirectoryWithName("dir1");
                    expect(entries).toContainFileWithName("file1");
                    expect(entries).not.toContainFileWithName("file2");
                });
            });

            it("should return an error if the directory doesn't exist", function () {
                var successCalled = false, errorCalled = false, error = null;
                NativeFileSystem.requestNativeFileSystem(this.path + '/nonexistent-dir', function (data) {
                    successCalled = true;
                }, function (err) {
                    errorCalled = true;
                    error = err;
                });

                waitsFor(function () { return successCalled || errorCalled; }, 1000);

                runs(function () {
                    expect(successCalled).toBe(false);
                    expect(errorCalled).toBe(true);
                    expect(error.code).toBe(FileError.NOT_FOUND_ERR);
                });
            });

            it("should return an error if you pass a bad parameter", function () {
                var successCalled = false, errorCalled = false, error = null;
                NativeFileSystem.requestNativeFileSystem(
                    0xDEADBEEF,
                    function (data) {
                        successCalled = true;
                    },
                    function (err) {
                        errorCalled = true;
                        error = err;
                    }
                );

                waitsFor(function () { return successCalled || errorCalled; }, 1000);

                runs(function () {
                    expect(successCalled).toBe(false);
                    expect(errorCalled).toBe(true);
                    expect(error.code).toBe(FileError.SECURITY_ERR);
                });
            });

            it("should be okay to not pass an error callback", function () {
                var entries = null;
                NativeFileSystem.requestNativeFileSystem(this.path, function (data) {
                    entries = data;
                });

                waitsFor(function () { return entries !== null; }, 1000);

                runs(function () {
                    expect(entries).not.toBe(null);
                });
            });

            it("can read an empty folder", function () {
                // TODO: (issue #241): Implement DirectoryEntry.getDirectory() and remove this empty folder workaround.
                // We need an empty folder for testing in this spec. Unfortunately, it's impossible
                // to check an empty folder in to git, and we don't have low level fs calls to create an emtpy folder.
                // So, for now, we have a folder called "emptydir" which contains a single 0-length file called
                // "placeholder". We delete that file at the beginning of each test, and then recreate it at the end.
                //
                // If we add NativeFileSystem commands to create a folder, we should change this test to simply create
                // a new folder (rather than remove a placeholder, etc.)
                var entries = null;
                var accessedFolder = false, placeholderDeleted = false, readComplete = false, gotErrorReadingContents = false, placeholderRecreated = false;

                var dirPath = this.path + "/emptydir";
                var placeholderPath = dirPath + "/placeholder";

                function requestNativeFileSystemSuccessCB(nfs) {
                    accessedFolder = true;

                    function recreatePlaceholder(successCallback) {
                        nfs.getFile("placeholder",
                                    { create: true, exclusive: true },
                                    function () { placeholderRecreated = true; },
                                    function () { placeholderRecreated = false; });
                    }

                    function readDirectory() {
                        var reader = nfs.createReader();
                        var successCallback = function (e) {
                            entries = e;
                            readComplete = true;
                            recreatePlaceholder();
                        };
                        var errorCallback = function () {
                            readComplete = true;
                            gotErrorReadingContents = true;
                            recreatePlaceholder();
                        };
                        reader.readEntries(successCallback, errorCallback);
                    }

                    function deletePlaceholder(successCallback) {
                        // TODO: (issue #241): implement FileEntry.remove()
                        // once NativeFileSystem has a delete/unlink, should use that
                        brackets.fs.unlink(placeholderPath, function (err) {
                            if (!err) {
                                placeholderDeleted = true;
                            }
                            // Even if there was an error, we want to read the directory
                            // because it could be that the placeholder is just missing.
                            // If we continue, we'll create the placeholder and the test
                            // will (maybe) pass next time
                            readDirectory();
                        });
                    }

                    deletePlaceholder(); // which calls readDirectory which calls recreatePlaceholder

                }

                var nfs = NativeFileSystem.requestNativeFileSystem(
                    dirPath,
                    requestNativeFileSystemSuccessCB,
                    function () { readComplete = true; }
                );

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

                runs(function () {
                    expect(accessedFolder).toBe(true);
                    expect(placeholderDeleted).toBe(true);
                    expect(gotErrorReadingContents).toBe(false);
                    expect(entries).toEqual([]);
                    expect(placeholderRecreated).toBe(true);
                });
            });

            it("should timeout with error when reading dir if low-level stat call takes too long", function () {
                var statCalled = false, readComplete = false, gotError = false, theError = null;
                var oldStat = brackets.fs.stat;
                this.after(function () { brackets.fs.stat = oldStat; });

                function requestNativeFileSystemSuccessCB(nfs) {
                    var reader = nfs.createReader();

                    var successCallback = function (e) { readComplete = true; };
                    var errorCallback = function (error) { readComplete = true; gotError = true; theError = error; };

                    // mock up new low-level stat that never calls the callback
                    brackets.fs.stat = function (path, callback) {
                        statCalled = true;
                    };

                    reader.readEntries(successCallback, errorCallback);

                }

                var nfs = NativeFileSystem.requestNativeFileSystem(this.path, requestNativeFileSystemSuccessCB);

                waitsFor(function () { return readComplete; }, NativeFileSystem.ASYNC_TIMEOUT * 2);

                runs(function () {
                    expect(readComplete).toBe(true);
                    expect(statCalled).toBe(true);
                    expect(gotError).toBe(true);
                    expect(theError.code).toBe(FileError.SECURITY_ERR);
                });
            });
        });

        describe("Reading a file", function () {
            it("should read a file from disk", function () {
                var gotFile = false, readFile = false, gotError = false, content;
                var fileEntry = new NativeFileSystem.FileEntry(this.path + "/file1");
                fileEntry.file(function (file) {
                    gotFile = true;
                    var reader = new NativeFileSystem.FileReader();
                    reader.onload = function (event) {
                        readFile = true;
                        content = event.target.result;
                    };
                    reader.onerror = function (event) {
                        gotError = true;
                    };
                    reader.readAsText(file, "utf8");
                });

                waitsFor(function () { return gotFile && readFile; }, 1000);

                runs(function () {
                    expect(gotFile).toBe(true);
                    expect(readFile).toBe(true);
                    expect(gotError).toBe(false);
                    expect(content).toBe(this.file1content);
                });
            });

            it("should return an error if the file is not found", function () {
                var gotFile = false, readFile = false, errorCode;
                var fileEntry = new NativeFileSystem.FileEntry(this.path + "/idontexist");
                fileEntry.file(function (file) {
                    gotFile = true;
                    var reader = new NativeFileSystem.FileReader();
                    reader.onload = function (event) {
                        readFile = true;
                    };
                    reader.onerror = function (event) {
                        errorCode = event.target.error.code;
                    };
                    reader.readAsText(file, "utf8");
                });

                waitsFor(function () { return gotFile && errorCode; }, 1000);

                runs(function () {
                    expect(gotFile).toBe(true);
                    expect(readFile).toBe(false);
                    expect(errorCode).toBe(FileError.NOT_FOUND_ERR);
                });
            });

            it("should fire appropriate events when the file is done loading", function () {
                var gotFile = false, gotLoad = false, gotLoadStart = false, gotLoadEnd = false,
                    gotProgress = false, gotError = false, gotAbort = false;
                var fileEntry = new NativeFileSystem.FileEntry(this.path + "/file1");
                fileEntry.file(function (file) {
                    gotFile = true;
                    var reader = new NativeFileSystem.FileReader();
                    reader.onload = function (event) {
                        gotLoad = true;
                    };
                    reader.onloadstart = function (event) {
                        gotLoadStart = true;
                    };
                    reader.onloadend = function (event) {
                        gotLoadEnd = true;
                    };
                    reader.onprogress = function (event) {
                        gotProgress = true;
                    };
                    reader.onerror = function (event) {
                        gotError = true;
                    };
                    reader.onabort = function (event) {
                        gotAbort = true;
                    };
                    reader.readAsText(file, "utf8");
                });

                waitsFor(function () { return gotLoad && gotLoadEnd && gotProgress; }, 1000);

                runs(function () {
                    expect(gotFile).toBe(true);
                    expect(gotLoadStart).toBe(true);
                    expect(gotLoad).toBe(true);
                    expect(gotLoadEnd).toBe(true);
                    expect(gotProgress).toBe(true);
                    expect(gotError).toBe(false);
                    expect(gotAbort).toBe(false);
                });
            });

            it("should return an error but not crash if you create a bad FileEntry", function () {
                var gotFile = false, readFile = false, gotError = false;
                var fileEntry = new NativeFileSystem.FileEntry(null);
                fileEntry.file(function (file) {
                    gotFile = true;
                    var reader = new NativeFileSystem.FileReader();
                    reader.onload = function (event) {
                        readFile = true;
                    };
                    reader.onerror = function (event) {
                        gotError = true;
                    };
                    reader.readAsText(file, "utf8");
                });

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

                runs(function () {
                    expect(gotFile).toBe(true);
                    expect(readFile).toBe(false);
                    expect(gotError).toBe(true);
                });
            });
        });

        describe("Writing", function () {

            beforeEach(function () {
                var nfs = null;
                var chmodDone = false;

                runs(function () {
                    NativeFileSystem.requestNativeFileSystem(this.path, function (fs) {
                        nfs = fs;
                    });
                });
                waitsFor(function () { return nfs; }, 1000);

                runs(function () {
                    this.nfs = nfs;
                });

                // set read-only permissions
                runs(function () {
                    brackets.fs.chmod(this.path + "/cant_read_here.txt", parseInt("222", 8), function (err) {
                        _err = err;
                        chmodDone = true;
                    });
                    brackets.fs.chmod(this.path + "/cant_write_here.txt", parseInt("444", 8), function (err) {
                        _err = err;
                        chmodDone = true;
                    });
                });
                waitsFor(function () { return chmodDone && (_err === brackets.fs.NO_ERROR); }, 1000);
            });

            afterEach(function () {
                var chmodDone = false;

                // restore permissions for git
                runs(function () {
                    brackets.fs.chmod(this.path + "/cant_read_here.txt", parseInt("777", 8), function (err) {
                        _err = err;
                        chmodDone = true;
                    });
                });
                waitsFor(function () { return chmodDone && (_err === brackets.fs.NO_ERROR); }, 1000);
            });

            it("should create new, zero-length files", function () {
                var fileEntry = null;
                var writeComplete = false;

                // create a new file exclusively
                runs(function () {
                    var successCallback = function (entry) {
                        fileEntry = entry;
                        writeComplete = true;
                    };
                    var errorCallback = function () {
                        writeComplete = true;
                    };

                    // FIXME (issue #247): NativeFileSystem.root is missing
                    this.nfs.getFile("new-zero-length-file.txt", { create: true, exclusive: true }, successCallback, errorCallback);
                });

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

                // fileEntry is non-null on success
                runs(function () {
                    expect(fileEntry).not.toBe(null);
                });

                var actualContents = null;

                // read the new file
                runs(function () {
                    brackets.fs.readFile(fileEntry.fullPath, "utf8", function (err, contents) {
                        actualContents = contents;
                    });
                });

                // wait for content to be read
                waitsFor(function () { return (actualContents !== null); }, 1000);

                // verify actual content to be empty
                var cleanupComplete = false;
                runs(function () {
                    expect(actualContents).toEqual("");

                    // cleanup
                    var self = this;
                    brackets.fs.unlink(fileEntry.fullPath, function (err) {
                        cleanupComplete = (err === brackets.fs.NO_ERROR);
                    });
                });

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

            it("should report an error when a file does not exist and create = false", function () {
                var fileEntry = null;
                var writeComplete = false;
                var error = null;

                // create a new file exclusively
                runs(function () {
                    var successCallback = function (entry) {
                        fileEntry = entry;
                        writeComplete = true;
                    };
                    var errorCallback = function (err) {
                        error = err;
                        writeComplete = true;
                    };

                    // FIXME (issue #247): NativeFileSystem.root is missing
                    this.nfs.getFile("does-not-exist.txt", { create: false }, successCallback, errorCallback);
                });

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

                // fileEntry is null on error
                runs(function () {
                    expect(fileEntry).toBe(null);
                    expect(error.code).toBe(FileError.NOT_FOUND_ERR);
                });
            });

            it("should return an error if file exists and exclusive is true", function () {
                var fileEntry = null;
                var writeComplete = false;
                var error = null;

                // try to create a new file exclusively when the file name already exists
                runs(function () {
                    var successCallback = function (entry) {
                        fileEntry = entry;
                        writeComplete = true;
                    };
                    var errorCallback = function (err) {
                        error = err;
                        writeComplete = true;
                    };

                    // FIXME (issue #247): NativeFileSystem.root is missing
                    this.nfs.getFile("file1", { create: true, exclusive: true }, successCallback, errorCallback);
                });

                // wait for success or error to return
                waitsFor(function () { return writeComplete; }, 1000);

                runs(function () {
                    // fileEntry will be null when errorCallback is handled
                    expect(fileEntry).toBe(null);

                    // errorCallback should be called with PATH_EXISTS_ERR
                    expect(error.code).toEqual(FileError.PATH_EXISTS_ERR);
                });
            });

            it("should return an error if the path is a directory", function () {
                var fileEntry = null;
                var writeComplete = false;
                var error = null;

                // try to write to a path that is a directory instead of a file
                runs(function () {
                    var successCallback = function (entry) {
                        fileEntry = entry;
                        writeComplete = true;
                    };
                    var errorCallback = function (err) {
                        error = err;
                        writeComplete = true;
                    };

                    // FIXME (issue #247): NativeFileSystem.root is missing
                    this.nfs.getFile("dir1", { create: false }, successCallback, errorCallback);
                });

                // wait for success or error to return
                waitsFor(function () { return writeComplete; }, 1000);

                runs(function () {
                    // fileEntry will be null when errorCallback is handled
                    expect(fileEntry).toBe(null);

                    // errorCallback should be called with TYPE_MISMATCH_ERR
                    expect(error.code).toEqual(FileError.TYPE_MISMATCH_ERR);
                });
            });

            it("should create overwrite files with new content", function () {
                var fileEntry = null;
                var writeComplete = false;
                var error = null;

                runs(function () {
                    var successCallback = function (entry) {
                        fileEntry = entry;

                        fileEntry.createWriter(function (fileWriter) {
                            fileWriter.onwriteend = function (e) {
                                writeComplete = true;
                            };
                            fileWriter.onerror = function (err) {
                                writeComplete = true;
                            };

                            // TODO (issue #241): BlobBulder
                            fileWriter.write("FileWriter.write");
                        });
                    };
                    var errorCallback = function () {
                        writeComplete = true;
                    };

                    this.nfs.getFile("file1", { create: false }, successCallback, errorCallback);
                });

                waitsFor(function () { return writeComplete && fileEntry; }, 1000);

                var actualContents = null;

                runs(function () {
                    brackets.fs.readFile(fileEntry.fullPath, "utf8", function (err, contents) {
                        actualContents = contents;
                    });
                });

                waitsFor(function () { return !!actualContents; }, 1000);

                var rewriteComplete = false;

                runs(function () {
                    expect(actualContents).toEqual("FileWriter.write");

                    // reset file1 content
                    // reset file1 content
                    brackets.fs.writeFile(this.path + "/file1", this.file1content, "utf8", function () {
                        rewriteComplete = true;
                    });
                });

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

            it("should write an empty file", function () {
                var fileEntry = null;
                var writeComplete = false;
                var error = null;

                runs(function () {
                    var successCallback = function (entry) {
                        fileEntry = entry;

                        fileEntry.createWriter(function (fileWriter) {
                            fileWriter.onwriteend = function (e) {
                                writeComplete = true;
                            };
                            fileWriter.onerror = function (err) {
                                writeComplete = true;
                                error = err;
                            };

                            // TODO (jasonsj): BlobBulder
                            fileWriter.write("");
                        });
                    };
                    var errorCallback = function () {
                        writeComplete = true;
                    };

                    this.nfs.getFile("file1", { create: false }, successCallback, errorCallback);
                });

                waitsFor(function () { return writeComplete && fileEntry; }, 1000);

                var actualContents = null;

                runs(function () {
                    brackets.fs.readFile(fileEntry.fullPath, "utf8", function (err, contents) {
                        actualContents = contents;
                    });
                });

                waitsFor(function () { return (actualContents !== null); }, 1000);

                var rewriteComplete = false;

                runs(function () {
                    expect(actualContents).toEqual("");

                    // reset file1 content
                    brackets.fs.writeFile(this.path + "/file1", this.file1content, "utf8", function () {
                        rewriteComplete = true;
                    });
                });

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

            it("should report an error when writing to a file that cannot be read", function () {
                var complete = false;
                var error = null;

                // createWriter() should return an error for files it can't read
                runs(function () {
                    this.nfs.getFile(
                        "cant_read_here.txt",
                        { create: false },
                        function (entry) {
                            entry.createWriter(
                                function () { complete = true; },
                                function (err) { error = err; }
                            );
                        }
                    );
                });
                waitsFor(function () { return complete || error; }, 1000);

                runs(function () {
                    expect(complete).toBeFalsy();
                    expect(error.code).toBe(FileError.NOT_READABLE_ERR);
                });
            });

            it("should report an error when writing to a file that cannot be written", function () {
                var writeComplete = false;
                var error = null;

                runs(function () {
                    var successCallback = function (entry) {
                        entry.createWriter(function (fileWriter) {
                            fileWriter.onwriteend = function (e) {
                                writeComplete = true;
                            };
                            fileWriter.onerror = function (err) {
                                writeComplete = true;
                                error = err;
                            };

                            // TODO (issue #241): BlobBulder
                            fileWriter.write("FileWriter.write");
                        });
                    };
                    var errorCallback = function () {
                        writeComplete = true;
                    };

                    this.nfs.getFile("cant_write_here.txt", { create: false }, successCallback, errorCallback);
                });

                // fileWriter.onerror handler should be invoked for read only files
                waitsFor(
                    function () {
                        return writeComplete
                            && error
                            && (error.code === FileError.NO_MODIFICATION_ALLOWED_ERR);
                    },
                    1000
                );
            });

            xit("should append to existing files", function () {
                this.fail("TODO (issue #241): not supported for sprint 1");
            });

            xit("should seek into a file before writing", function () {
                this.fail("TODO (issue #241): not supported for sprint 1");
            });

            xit("should truncate files", function () {
                this.fail("TODO (issue #241): not supported for sprint 1");
            });
        });
    });
});


keep keep keep keep keep replace keep keep keep keep

* @param {string} hint what is getting edited and should be hinted

* @param {number} offset where the cursor is for the part getting hinted

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


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

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

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

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

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

// COLORS
// ------

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

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

// MISC
// ----

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

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

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

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

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

}


/*
    http://www.JSON.org/json2.js
    2009-08-17

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html

    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or ' '),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the value

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'

            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'

        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });

    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/

/*jslint evil: true */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/

"use strict";

// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (!this.JSON) {
    this.JSON = {};
}

(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                   this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z' : null;
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;

    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }

    function str(key, holder) {
// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }

// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }

// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());


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

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

/**
 * Manages the mapping of keyboard inputs to commands.
 */
define(function (require, exports, module) {
    'use strict';

    var CommandManager = require("CommandManager");

    /**
     * The currently installed keymap.
     */
    var _keymap = null;

    /**
     * Install the specified keymap as the current keymap, overwriting the existing keymap.
     *
     * @param {KeyMap} keymap The keymap to install.
     */
    function installKeymap(keymap) {
        _keymap = keymap;
    }

    /**
     * Process the keybinding for the current key.
     *
     * @param {string} A key-description string.
     * @return {boolean} true if the key was processed, false otherwise
     */
    function handleKey(key) {
        if (_keymap && _keymap.map[key]) {
            CommandManager.execute(_keymap.map[key]);
            return true;
        }
        return false;
    }

    // Define public API
    exports.installKeymap = installKeymap;
    exports.handleKey = handleKey;
});


*              about the current tag hint.

function createTagInfo(token, offset, tagName, attrName, attrValue) {

keep keep keep keep replace keep keep keep keep

function createTagInfo(type, offset, tagName, attrName, attrValue) {


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

    /**
     * @private
     * builds the keyDescriptor string from the given parts
     * @param {boolean} hasCtrl Is Ctrl key enabled
     * @param {boolean} hasAlt Is Alt key enabled
     * @param {boolean} hasShift Is Shift key enabled
     * @param {string} key The key that's pressed
     * @return {string} The normalized key descriptor
     */
    function _buildKeyDescriptor(hasCtrl, hasAlt, hasShift, key) {
        if (!key) {
            console.log("KeyMap _buildKeyDescriptor() - No key provided!");
            return "";
        }

        var keyDescriptor = [];
        if (hasCtrl) {
            keyDescriptor.push("Ctrl");
        }
        if (hasAlt) {
            keyDescriptor.push("Alt");
        }
        if (hasShift) {
            keyDescriptor.push("Shift");
        }
        keyDescriptor.push(key);

        return keyDescriptor.join("-");
    }

    /**
     * @private
     * normalizes the incoming key descriptor so the modifier keys are always specified in the correct order
     * @param {string} The string for a key descriptor, can be in any order, the result will be Ctrl-Alt-Shift-<Key>
     * @return {string} The normalized key descriptor
     */
    function _normalizeKeyDescriptorString(origDescriptor) {
        var hasCtrl = false,
            hasAlt = false,
            hasShift = false,
            key = "";

        function _isModifier(left, right, previouslyFound) {
            if (!left || !right) {
                return false;
            }
            left = left.trim().toLowerCase();
            right = right.trim().toLowerCase();
            var matched = (left.length > 0 && left === right);
            if (matched && previouslyFound) {
                console.log("KeyMap _normalizeKeyDescriptorString() - Modifier defined twice: " + origDescriptor);
            }
            return matched;
        }

        origDescriptor.split("-").forEach(function parseDescriptor(ele, i, arr) {
            if (_isModifier("ctrl", ele, hasCtrl)) {
                hasCtrl = true;
            } else if (_isModifier("cmd", ele, hasCtrl)) {
                console.log("KeyMap _normalizeKeyDescriptorString() - Cmd getting mapped to Ctrl from: " + origDescriptor);
                hasCtrl = true;
            } else if (_isModifier("alt", ele, hasAlt)) {
                hasAlt = true;
            } else if (_isModifier("opt", ele, hasAlt)) {
                console.log("KeyMap _normalizeKeyDescriptorString() - Opt getting mapped to Alt from: " + origDescriptor);
                hasAlt = true;
            } else if (_isModifier("shift", ele, hasShift)) {
                hasShift = true;
            } else if (key.length > 0) {
                console.log("KeyMap _normalizeKeyDescriptorString() - Multiple keys defined. Using key: " + key + " from: " + origDescriptor);
            } else {
                key = ele;
            }
        });

        return _buildKeyDescriptor(hasCtrl, hasAlt, hasShift, key);
    }

    /**
     * @private
     * normalizes the incoming map so all the key descriptors are specified the correct way
     * @param {map} map The string for a key descriptor, can be in any order, the result will be Ctrl-Alt-Shift-<Key>
     * @return {map} The normalized map
     */
    function _normalizeMap(map) {
        var finalMap = {};
        Object.keys(map).forEach(function normalizeKey(ele, i, arr) {
            var val = map[ele];
            var normalizedKey = _normalizeKeyDescriptorString(ele);
            if (normalizedKey.length === 0) {
                console.log("KeyMap _normalizeMap() - Rejecting malformed key: " + ele + " (value: " + val + ")");
            } else if (!val) {
                console.log("KeyMap _normalizeMap() - Rejecting key for falsy value: " + ele + " (value: " + val + ")");
            } else if (finalMap[normalizedKey]) {
                console.log("KeyMap _normalizeMap() - Rejecting key because it was defined twice: " + ele + " (value: " + val + ")");
            } else {
                if (normalizedKey !== ele) {
                    console.log("KeyMap _normalizeMap() - Corrected a malformed key: " + ele + " (value: " + val + ")");
                }
                finalMap[normalizedKey] = val;
            }
        });
        return finalMap;
    }

    /**
     * @private
     * given a list of bindings, goes through and turns them into a map. The list is filter based
     * on platform, so if I binding has no specific platform or needs to match the given platform
     * @param [{bindings}] bindings A list of binding objects
     * @param {string} platform The platform to filter on
     */
    function _bindingsToMap(bindings, platform) {
        var map = {};
        bindings.forEach(function transformToMap(ele, i, arr) {
            var keys = Object.keys(ele);
            var platformIndex = keys.indexOf("platform");
            if (platformIndex > -1) {
                if (ele.platform !== platform) {
                    return; //don't add this to the map
                }
                keys.splice(platformIndex, 1);
            }
            if (keys.length !== 1) {
                console.log("KeyMap _bindingsToMap() - bindings list has unknown keys: " + bindings);
                return;
            }
            var key = keys[0];
            map[key] = ele[key];
        });
        return map;
    }

    /** class Keymap
     *
     * A keymap specifies how keys are mapped to commands. This currently just holds the map, but in future
     * it will likely be extended to include other metadata about the keymap.
     *
     * Keys are described by strings of the form "[modifier-modifier-...-]key", where modifier is one of
     * Ctrl, Alt, or Shift. If multiple modifiers are specified, they will get normalized to the form
     * "Ctrl-Alt-Shift-<Key>" so modifiers are always stored and lookedup in that order.
     *    -- Ctrl maps to Cmd on Mac. (This means that you can't specifically bind to the Ctrl key on Mac.)
     *    -- Alt maps to the Option key on Mac.
     *    -- Letters must be uppercase, but do not require Shift by default. To indicate that Shift must be held
     *       down, you must specifically include Shift.
     *
     * @constructor
     * @param {args} An object with a list of objects mapping key-description strings to command IDs and a platform
     */
    var KeyMap = function (args) {
        if (args === undefined) {
            throw new Error("All parameters to the KeyMap constructor must be specified");
        }
        this.map = _normalizeMap(_bindingsToMap(args.bindings, args.platform));
    };

    /**
     * simple creator
     * @param {map} map An object mapping key-description strings to command IDs.
     */
    function create(map) {
        return new KeyMap(map);
    }

    /**
     * Takes a keyboard event and translates it into a key in a key map
     */
    function translateKeyboardEvent(event) {
        var hasCtrl = (event.metaKey || event.ctrlKey),
            hasAlt = (event.altKey),
            hasShift = (event.shiftKey),
            key = String.fromCharCode(event.keyCode);

        //From the W3C, if we can get the KeyboardEvent.keyIdentifier then look here
        //As that will let us use keys like then function keys "F5" for commands. The
        //full set of values we can use is here
        //http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
        var ident = event.originalEvent.keyIdentifier;
        if (ident) {
            if (ident.charAt(0) === "U" && ident.charAt(1) === "+") {
                //This is a unicode code point like "U+002A", get the 002A and use that
                key = String.fromCharCode(parseInt(ident.substring(2), 16));
            } else {
                //This is some non-character key, just use the raw identifier
                key = ident;
            }
        }

        return _buildKeyDescriptor(hasCtrl, hasAlt, hasShift, key);
    }

    // Define public API
    exports.create = create;
    exports.translateKeyboardEvent = translateKeyboardEvent;
});


/* Brackets mixins */

.box {
    display: -webkit-box;
    display: -moz-box;
    display: box;
}
.hbox {
    .box;
    -webkit-box-orient: horizontal;
    -moz-box-orient: horizontal;
    box-orient: horizontal;
}
.vbox {
    .box;
    -webkit-box-orient: vertical;
    -moz-box-orient: vertical;
    box-orient: vertical;
}
.box-flex(@ratio) {
    -webkit-box-flex: @ratio;
    -moz-box-flex: @ratio;
    box-flex: @ratio;
}
.box-pack(@pack) {
    -webkit-box-pack: @pack;
    -moz-box-pack: @pack;
    box-pack: @pack;
}
.box-align(@align) {
    -webkit-box-align: @align;
    -moz-box-align: @align;
    box-align: @align;
}
.sane-box-model {
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;
}


keep keep keep keep replace replace replace keep keep


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


<!doctype html>

    <meta charset="utf-8">
    <title>Brackets</title>

    <link rel="stylesheet" href="thirdparty/CodeMirror2/lib/util/dialog.css">

    <link rel="stylesheet/less" href="brackets.less">

            Brackets

                    File

New

Open

Close

Save

Quit

                    Debug

Run Tests

                                Enable JSLint
                                ✓

Show Perf Data

                        <button id="btn-open-project" class="btn">Open</button>

                        Open Files

                        Project Files

                    ★

[  ]

JSLint errors

            ×

Error

Message goes here

            OK

            ×

Save Changes

Message goes here

            Don't Save
            Cancel
            Save

Title goes here

Message goes here

            Reload from Disk
            Keep Changes in Editor

Title goes here

Message goes here

            Close (Don't Save)
            Keep Changes in Editor

x


keep keep replace keep keep keep keep keep

* Gets the taginfo starting from the attribute value and moving backwards

* @param {editor:{CodeMirror}, pos:{ch:{string}, line:{number}}, token:{object}} context