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

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

    var FileUtils       = require("file/FileUtils"),
        EventDispatcher = require("utils/EventDispatcher"),
        FindUtils       = require("search/FindUtils"),
        MainViewManager = require("view/MainViewManager");

    /**
     * @constructor
     * Manages a set of search query and result data.
     * Dispatches these events:
     *      "change" - whenever the results have been updated. Note that it's up to people who
     *      edit the model to call fireChange() when necessary - it doesn't automatically fire.
     */
    function SearchModel() {
        this.clear();
    }
    EventDispatcher.makeEventDispatcher(SearchModel.prototype);

    /** @const Constant used to define the maximum results found.
     *  Note that this is a soft limit - we'll likely go slightly over it since
     *  we always add all the searches in a given file.
     */
    SearchModel.MAX_TOTAL_RESULTS = 100000;

    /**
     * The current set of results.
     * @type {Object.<fullPath: string, {matches: Array.<Object>, collapsed: boolean, timestamp: Date}>}
     */
    SearchModel.prototype.results = null;

    /**
     * The query that generated these results.
     * @type {{query: string, caseSensitive: boolean, isRegexp: boolean}}
     */
    SearchModel.prototype.queryInfo = null;

    /**
     * The compiled query, expressed as a regexp.
     * @type {RegExp}
     */
    SearchModel.prototype.queryExpr = null;

    /**
     * Whether this is a find/replace query.
     * @type {boolean}
     */
    SearchModel.prototype.isReplace = false;

    /**
     * The replacement text specified for this query, if any.
     * @type {string}
     */
    SearchModel.prototype.replaceText = null;

    /**
     * The file/folder path representing the scope that this query was performed in.
     * @type {FileSystemEntry}
     */
    SearchModel.prototype.scope = null;

    /**
     * A file filter (as returned from FileFilters) to apply within the main scope.
     * @type {string}
     */
    SearchModel.prototype.filter = null;

    /**
     * The total number of matches in the model.
     * @type {number}
     */
    SearchModel.prototype.numMatches = 0;

    /**
     * Whether or not we hit the maximum number of results for the type of search we did.
     * @type {boolean}
     */
    SearchModel.prototype.foundMaximum = false;

    /**
     * Whether or not we exceeded the maximum number of results in the search we did.
     * @type {boolean}
     */
    SearchModel.prototype.exceedsMaximum = false;

    /**
     * Clears out the model to an empty state.
     */
    SearchModel.prototype.clear = function () {
        var numMatchesBefore = this.numMatches;
        this.results = {};
        this.queryInfo = null;
        this.queryExpr = null;
        this.isReplace = false;
        this.replaceText = null;
        this.scope = null;
        this.numMatches = 0;
        this.foundMaximum = false;
        this.exceedsMaximum = false;
        if (numMatchesBefore !== 0) {
            this.fireChanged();
        }
    };

    /**
     * Sets the given query info and stores a compiled RegExp query in this.queryExpr.
     * @param {{query: string, caseSensitive: boolean, isRegexp: boolean}} queryInfo
     * @return {boolean} true if the query was valid and properly set, false if it was
     *      invalid or empty.
     */
    SearchModel.prototype.setQueryInfo = function (queryInfo) {
        var parsedQuery = FindUtils.parseQueryInfo(queryInfo);
        if (parsedQuery.valid) {
            this.queryInfo = queryInfo;
            this.queryExpr = parsedQuery.queryExpr;
            return true;
        } else {
            return false;
        }
    };

    /**
     * Sets the list of matches for the given path, removing the previous match info, if any, and updating
     * the total match count. Note that for the count to remain accurate, the previous match info must not have
     * been mutated since it was set.
     * @param {string} fullpath Full path to the file containing the matches.
     * @param {!{matches: Object, timestamp: Date, collapsed: boolean=}} resultInfo Info for the matches to set:
     *      matches - Array of matches, in the format returned by FindInFiles._getSearchMatches()
     *      timestamp - The timestamp of the document at the time we searched it.
     *      collapsed - Optional: whether the results should be collapsed in the UI (default false).
     */
    SearchModel.prototype.setResults = function (fullpath, resultInfo) {
        this.removeResults(fullpath);

        if (this.foundMaximum || !resultInfo.matches.length) {
            return;
        }

        // Make sure that the optional `collapsed` property is explicitly set to either true or false,
        // to avoid logic issues later with comparing values.
        resultInfo.collapsed = !!resultInfo.collapsed;

        this.results[fullpath] = resultInfo;
        this.numMatches += resultInfo.matches.length;
        if (this.numMatches >= SearchModel.MAX_TOTAL_RESULTS) {
            this.foundMaximum = true;

            // Remove final result if there have been over MAX_TOTAL_RESULTS found
            if (this.numMatches > SearchModel.MAX_TOTAL_RESULTS) {
                this.results[fullpath].matches.pop();
                this.numMatches--;
                this.exceedsMaximum = true;
            }
        }
    };

    /**
     * Removes the given result's matches from the search results and updates the total match count.
     * @param {string} fullpath Full path to the file containing the matches.
     */
    SearchModel.prototype.removeResults = function (fullpath) {
        if (this.results[fullpath]) {
            this.numMatches -= this.results[fullpath].matches.length;
            delete this.results[fullpath];
        }
    };

    /**
     * @return {boolean} true if there are any results in this model.
     */
    SearchModel.prototype.hasResults = function () {
        return Object.keys(this.results).length > 0;
    };

    /**
     * Counts the total number of matches and files
     * @return {{files: number, matches: number}}
     */
    SearchModel.prototype.countFilesMatches = function () {
        return {files: (this.numFiles || Object.keys(this.results).length), matches: this.numMatches};
    };

    /**
     * Prioritizes the open file and then the working set files to the starting of the list of files
     * If node search is disabled, we sort the files too- Sorting is computation intensive, and our
     * ProjectManager.getAllFiles with the sort flag is not working properly : TODO TOFIX
     * @param {?string} firstFile If specified, the path to the file that should be sorted to the top.
     * @return {Array.<string>}
     */
    SearchModel.prototype.prioritizeOpenFile = function (firstFile) {
        var workingSetFiles = MainViewManager.getWorkingSet(MainViewManager.ALL_PANES),
            workingSetFileFound = {},
            fileSetWithoutWorkingSet = [],
            startingWorkingFileSet = [],
            propertyName = "",
            i = 0;

        if (FindUtils.isNodeSearchDisabled()) {
            return Object.keys(this.results).sort(function (key1, key2) {
                if (firstFile === key1) {
                    return -1;
                } else if (firstFile === key2) {
                    return 1;
                }
                return FileUtils.comparePaths(key1, key2);
            });
        }

        firstFile = firstFile || "";

        // Create a working set path map which indicates if a file in working set is found in file list
        for (i = 0; i < workingSetFiles.length; i++) {
            workingSetFileFound[workingSetFiles[i].fullPath] = false;
        }

        // Remove all the working set files from the filtration list
        fileSetWithoutWorkingSet = Object.keys(this.results).filter(function (key) {
            if (workingSetFileFound[key] !== undefined) {
                workingSetFileFound[key] = true;
                return false;
            }
            return true;
        });

        //push in the first file
        if (workingSetFileFound[firstFile] === true) {
            startingWorkingFileSet.push(firstFile);
            workingSetFileFound[firstFile] = false;
        }
        //push in the rest of working set files already present in file list
        for (propertyName in workingSetFileFound) {
            if (workingSetFileFound.hasOwnProperty(propertyName) && workingSetFileFound[propertyName]) {
                startingWorkingFileSet.push(propertyName);
            }
        }
        return startingWorkingFileSet.concat(fileSetWithoutWorkingSet);
    };

    /**
     * Notifies listeners that the set of results has changed. Must be called after the
     * model is changed.
     * @param {boolean} quickChange Whether this type of change is one that might occur
     *      often, meaning that the view should buffer updates.
     */
    SearchModel.prototype.fireChanged = function (quickChange) {
        this.trigger("change", quickChange);
    };

    // Public API
    exports.SearchModel = SearchModel;
});


ColorEditor.prototype._normalizeColorString = function (color) {

// Convert from 0x notation into hex format string

return color.replace("0x","#") || color;

// Convert 6-digit hex to 3-digit hex as TinyColor (#ffaacc -> #fac)

keep keep keep replace replace replace replace keep keep keep keep

Thumbs.db

# ignore jenkins build info
/build.prop

# ignore node_modules created by grunt, but not more deeply-nested node_modules
/node_modules
/npm-debug.log

# ignore node_modules inside src
/src/node_modules

# ignore files copied from node_modules to src/thirdparty
/src/thirdparty/CodeMirror
/src/thirdparty/less.min.js

# ignore compiled files
/dist
/src/.index.html
/src/styles/brackets.min.css
/src/styles/brackets.min.css.map

# ignore everything in the dev extension directory EXCEPT the README
# (so that the directory is non-empty and can be in git)
/src/extensions/dev/*
!/src/extensions/dev/README.*

/src/extensions/disabled

# ignore .disabled file for default extensions
/src/extensions/default/*/.disabled

#OSX .DS_Store files
.DS_Store

# unit test working directory
/test/results
/test/temp

# Netbeans
/nbproject

# PhpStorm
.idea

# Files that can be automatically downloaded that we don't want to ship with our builds
/src/extensibility/node/node_modules/request/tests/


//
// Code (inline and blocK)
// --------------------------------------------------

// Inline and block code styles
code,
pre {
  padding: 0 3px 2px;
  #font > #family > .monospace;
  font-size: @baseFontSize - 2;
  color: @grayDark;
  .border-radius(3px);
}

// Inline code
code {
  padding: 2px 4px;
  color: #d14;
  background-color: #f7f7f9;
  border: 1px solid #e1e1e8;
  white-space: nowrap;
}

// Blocks of code
pre {
  display: block;
  padding: (@baseLineHeight - 1) / 2;
  margin: 0 0 @baseLineHeight / 2;
  font-size: @baseFontSize - 1; // 14px to 13px
  line-height: @baseLineHeight;
  word-break: break-all;
  word-wrap: break-word;
  white-space: pre;
  white-space: pre-wrap;
  background-color: #f5f5f5;
  border: 1px solid #ccc; // fallback for IE7-8
  border: 1px solid rgba(0,0,0,.15);
  .border-radius(@baseBorderRadius);

  // Make prettyprint styles more spaced out for readability
  &.prettyprint {
    margin-bottom: @baseLineHeight;
  }

  // Account for some code outputs that place code tags in pre tags
  code {
    padding: 0;
    color: inherit;
    white-space: pre;
    white-space: pre-wrap;
    background-color: transparent;
    border: 0;
  }
}

// Enable scrollable blocks of code
.pre-scrollable {
  max-height: 340px;
  overflow-y: scroll;
}


keep keep keep keep replace keep keep keep keep

newColorOk = (newColorObj.toString() === this._normalizeColorString(checkSetFormat(newColor, true)));

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

/*jslint regexp: true */

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

    var KeyEvent           = brackets.getModule("utils/KeyEvent"),
        PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
        StringUtils        = brackets.getModule("utils/StringUtils"),
        Strings            = brackets.getModule("strings"),
        Mustache           = brackets.getModule("thirdparty/mustache/mustache"),
        tinycolor          = require("thirdparty/tinycolor-min");

    /** Mustache template that forms the bare DOM structure of the UI */
    var ColorEditorTemplate = require("text!ColorEditorTemplate.html");

    /**
     * @const @type {number}
     */
    var STEP_MULTIPLIER = 5;

    /**
     * Convert 0x notation into hex6 format for tinycolor
     * compatibility: ("0xFFAACC" => "#FFFFFF")
     */
    function color0xToHex(color,convertToStr){
        var format0xToHexColor = color.replace("0x","#");
        var hexColor = tinycolor(format0xToHexColor);
        hexColor._format = "0x";

        if(convertToStr){
            return hexColor.toString();
        }
        return hexColor;
    }

    function as0xString(color){
        return color.toHexString().replace("#","0x");
    }

    function checkSetFormat(color,convertToStr){
        if ((/^0x/).test(color)) {
            return color0xToHex(color,convertToStr);
        }
        return tinycolor(color);
    }

    /**
     * Color picker control; may be used standalone or within an InlineColorEditor inline widget.
     * @param {!jQuery} $parent  DOM node into which to append the root of the color picker UI
     * @param {!string} color  Initially selected color
     * @param {!function(string)} callback  Called whenever selected color changes
     * @param {!Array.<{value:string, count:number}>} swatches  Quick-access color swatches to include in UI
     */
    function ColorEditor($parent, color, callback, swatches) {
        // Create the DOM structure, filling in localized strings via Mustache
        this.$element = $(Mustache.render(ColorEditorTemplate, Strings));
        $parent.append(this.$element);

        this._callback = callback;

        this._handleKeydown = this._handleKeydown.bind(this);
        this._handleOpacityKeydown = this._handleOpacityKeydown.bind(this);
        this._handleHslKeydown = this._handleHslKeydown.bind(this);
        this._handleHueKeydown = this._handleHueKeydown.bind(this);
        this._handleSelectionKeydown = this._handleSelectionKeydown.bind(this);
        this._handleOpacityDrag = this._handleOpacityDrag.bind(this);
        this._handleHueDrag = this._handleHueDrag.bind(this);
        this._handleSelectionFieldDrag = this._handleSelectionFieldDrag.bind(this);

        this._originalColor = color;
        this._color = checkSetFormat(color);

        this._redoColor = null;
        this._isUpperCase = PreferencesManager.get("uppercaseColors");
        PreferencesManager.on("change", "uppercaseColors", function () {
            this._isUpperCase = PreferencesManager.get("uppercaseColors");
        }.bind(this));

        this.$colorValue = this.$element.find(".color-value");
        this.$buttonList = this.$element.find("ul.button-bar");
        this.$rgbaButton = this.$element.find(".rgba");
        this.$hexButton = this.$element.find(".hex");
        this.$hslButton = this.$element.find(".hsla");
        this.$0xButton = this.$element.find(".0x");
        this.$currentColor = this.$element.find(".current-color");
        this.$originalColor = this.$element.find(".original-color");
        this.$selection = this.$element.find(".color-selection-field");
        this.$selectionBase = this.$element.find(".color-selection-field .selector-base");
        this.$hueBase = this.$element.find(".hue-slider .selector-base");
        this.$opacityGradient = this.$element.find(".opacity-gradient");
        this.$hueSlider = this.$element.find(".hue-slider");
        this.$hueSelector = this.$element.find(".hue-slider .selector-base");
        this.$opacitySlider = this.$element.find(".opacity-slider");
        this.$opacitySelector = this.$element.find(".opacity-slider .selector-base");
        this.$swatches = this.$element.find(".swatches");

        // Create quick-access color swatches
        this._addSwatches(swatches);

        // Attach event listeners to main UI elements
        this._addListeners();

        this._commitColor(color);
        this.$originalColor.css("background-color", checkSetFormat(this._originalColor));
    }

    /**
     * A string or tinycolor object representing the currently selected color
     * TODO (#2201): type is unpredictable
     * @type {tinycolor|string}
     */
    ColorEditor.prototype._color = null;

    /**
     * An HSV representation of the currently selected color.
     * TODO (#2201): type of _hsv.s/.v is unpredictable
     * @type {!{h:number, s:number|string, v:number|string, a:number}}
     */
    ColorEditor.prototype._hsv = tinycolor("rgba(0,0,0,1)").toHsv();

    /**
     * Color that was selected before undo(), if undo was the last change made. Else null.
     * @type {?string}
     */
    ColorEditor.prototype._redoColor = null;

    /**
     * Initial value the color picker was opened with
     * @type {!string}
     */
    ColorEditor.prototype._originalColor = null;

    /** Returns the root DOM node of the ColorPicker UI */
    ColorEditor.prototype.getRootElement = function () {
        return this.$element;
    };

    /** Attach event listeners for main UI elements */
    ColorEditor.prototype._addListeners = function () {
        this._bindColorFormatToRadioButton("rgba");
        this._bindColorFormatToRadioButton("hex");
        this._bindColorFormatToRadioButton("hsla");
        this._bindColorFormatToRadioButton("0x");

        this._bindInputHandlers();

        this._bindOriginalColorButton();

        this._registerDragHandler(this.$selection, this._handleSelectionFieldDrag);
        this._registerDragHandler(this.$hueSlider, this._handleHueDrag);
        this._registerDragHandler(this.$opacitySlider, this._handleOpacityDrag);
        this._bindKeyHandler(this.$selectionBase, this._handleSelectionKeydown);
        this._bindKeyHandler(this.$hueBase, this._handleHueKeydown);
        this._bindKeyHandler(this.$opacitySelector, this._handleOpacityKeydown);
        this._bindKeyHandler(this.$hslButton, this._handleHslKeydown);

        // General key handler gets bubbling events from any focusable part of widget
        this._bindKeyHandler(this.$element, this._handleKeydown);
    };

    /**
     * Update all UI elements to reflect the selected color (_color and _hsv). It is usually
     * incorrect to call this directly; use _commitColor() or setColorAsHsv() instead.
     */

    ColorEditor.prototype._synchronize = function () {
        var colorValue  = this.getColor().getOriginalInput();
        var colorObject = checkSetFormat(colorValue);
        var hueColor    = "hsl(" + this._hsv.h + ", 100%, 50%)";
        this._updateColorTypeRadioButtons(colorObject.getFormat());
        this.$colorValue.val(colorValue);
        this.$currentColor.css("background-color", checkSetFormat(colorValue,true));
        this.$selection.css("background-color", hueColor);
        this.$hueBase.css("background-color", hueColor);

        // Update gradients in color square & opacity slider
        this.$selectionBase.css("background-color", colorObject.toHexString());
        this.$opacityGradient.css("background-image", "linear-gradient(" + hueColor + ", transparent)");

        // Update slider thumb positions
        this.$hueSelector.css("bottom", (this._hsv.h / 360 * 100) + "%");
        this.$opacitySelector.css("bottom", (this._hsv.a * 100) + "%");
        if (!isNaN(this._hsv.s)) {      // TODO (#2201): type of _hsv.s/.v is unpredictable
            this._hsv.s = (this._hsv.s * 100) + "%";
        }
        if (!isNaN(this._hsv.v)) {
            this._hsv.v = (this._hsv.v * 100) + "%";
        }
        this.$selectionBase.css({
            left: this._hsv.s,
            bottom: this._hsv.v
        });
    };

    /**
     * Focus the main color square's thumb.
     * @return {boolean} True if we focused the square, false otherwise.
     */
    ColorEditor.prototype.focus = function () {
        if (!this.$selectionBase.is(":focus")) {
            this.$selectionBase.focus();
            return true;
        }
        return false;
    };

    /**
     * Remove any preference listeners before destroying the editor.
     */
    ColorEditor.prototype.destroy = function () {
        PreferencesManager.off("change", "uppercaseColors");
    };

    /**
     * @return {tinycolor|string} The currently selected color (TODO (#2201): type is unpredictable).
     */
    ColorEditor.prototype.getColor = function () {
        return this._color;
    };

    /** Update the format button bar's selection */
    ColorEditor.prototype._updateColorTypeRadioButtons = function (format) {
        this.$buttonList.find("li").removeClass("selected");
        switch (format) {
        case "rgb":
            this.$buttonList.find(".rgba").parent().addClass("selected");
            break;
        case "hex":
        case "name":
            this.$buttonList.find(".hex").parent().addClass("selected");
            break;
        case "hsl":
            this.$buttonList.find(".hsla").parent().addClass("selected");
            break;
        case "0x":
            this.$buttonList.find(".0x").parent().addClass("selected");
            break;
        }
    };

    /** Add event listeners to the format button bar */
    ColorEditor.prototype._bindColorFormatToRadioButton = function (buttonClass, propertyName, value) {
        var handler,
            self = this;
        handler = function (event) {
            var newFormat   = $(event.currentTarget).html().toLowerCase().replace("%", "p"),
                newColor    = self.getColor().toString();

            var colorObject = checkSetFormat(newColor);

            switch (newFormat) {
            case "hsla":
                newColor = colorObject.toHslString();
                break;
            case "rgba":
                newColor = colorObject.toRgbString();
                break;
            case "prgba":
                newColor = colorObject.toPercentageRgbString();
                break;
            case "hex":
                newColor = colorObject.toHexString();
                self._hsv.a = 1;
                break;
            case "0x":
                newColor = as0xString(colorObject);
                self._hsv.a = 1;
                self._format = "0x";
                break;
            }

            // We need to run this again whenever RGB/HSL/Hex conversions
            // are performed to preserve the case
            newColor = self._isUpperCase ? newColor.toUpperCase() : newColor;
            self._commitColor(newColor, false);
        };
        this.$element.find("." + buttonClass).click(handler);
    };

    /** Add event listener to the "original color value" swatch */
    ColorEditor.prototype._bindOriginalColorButton = function () {
        var self = this;
        this.$originalColor.click(function (event) {
            self._commitColor(self._originalColor, true);
        });
    };

    /**
     * Convert percentage values in an RGB color into normal RGB values in the range of 0 - 255.
     * If the original color is already in non-percentage format, does nothing.
     * @param {string} color The color to be converted to non-percentage RGB color string.
     * @return {string} an RGB color string in the normal format using non-percentage values
     */
    ColorEditor.prototype._convertToNormalRGB = function (color) {
        var matches = color.match(/^rgb.*?([0-9]+)\%.*?([0-9]+)\%.*?([0-9]+)\%/i);
        if (matches) {
            var i, percentStr, value;
            for (i = 0; i < 3; i++) {
                percentStr = matches[i + 1];
                value = Math.round(255 * Number(percentStr) / 100);
                if (!isNaN(value)) {
                    color = color.replace(percentStr + "%", value);
                }
            }
        }
        return color;
    };

    /**
     * Normalize the given color string into the format used by tinycolor, by adding a space
     * after commas.
     * @param {string} color The color to be corrected if it looks like an RGB or HSL color.
     * @return {string} a normalized color string.
     */
    ColorEditor.prototype._normalizeColorString = function (color) {
        var normalizedColor = color;

        // Convert from 0x notation into hex format string
        if (color.match(/^0x[0-9a-fA-F]{6}/)) {
            return color.replace("0x","#") || color;
        }
        // Convert 6-digit hex to 3-digit hex as TinyColor (#ffaacc -> #fac)
        if (color.match(/^#[0-9a-fA-F]{6}/)) {
            return tinycolor(color).toString();
        }
        if (color.match(/^(rgb|hsl)/i)) {
            normalizedColor = normalizedColor.replace(/,\s*/g, ", ");
            normalizedColor = normalizedColor.replace(/\(\s+/, "(");
            normalizedColor = normalizedColor.replace(/\s+\)/, ")");
        }
        return normalizedColor;
    };

    /** Handle changes in text field */
    ColorEditor.prototype._handleTextFieldInput = function (losingFocus) {
        var newColor    = $.trim(this.$colorValue.val()),
            newColorObj = checkSetFormat(newColor),
            newColorOk  = newColorObj.isValid();

        // TinyColor will auto correct an incomplete rgb or hsl value into a valid color value.
        // eg. rgb(0,0,0 -> rgb(0, 0, 0)
        // We want to avoid having TinyColor do this, because we don't want to sync the color
        // to the UI if it's incomplete. To accomplish this, we first normalize the original
        // color string into the format TinyColor would generate, and then compare it to what
        // TinyColor actually generates to see if it's different. If so, then we assume the color
        // was incomplete to begin with.
        if (newColorOk) {
            newColorOk = (newColorObj.toString() === this._normalizeColorString(newColor));
        }

        // Restore to the previous valid color if the new color is invalid or incomplete.
        if (losingFocus && !newColorOk) {
            newColor = this.getColor().toString();
        }

        // Sync only if we have a valid color or we're restoring the previous valid color.
        if (losingFocus || newColorOk) {
            this._commitColor(newColor, true);
        }
    };

    ColorEditor.prototype._bindInputHandlers = function () {
        var self = this;

        this.$colorValue.bind("input", function (event) {
            self._handleTextFieldInput(false);
        });

        this.$colorValue.bind("change", function (event) {
            self._handleTextFieldInput(true);
        });
    };

    /**
     * Populate the UI with the given color swatches and add listeners so they're selectable.
     * @param {!Array.<{value:string, count:number}>} swatches
     */
    ColorEditor.prototype._addSwatches = function (swatches) {
        var self = this;

        // Create swatches
        swatches.forEach(function (swatch) {
            var swatchValue = swatch.value.replace("0x","#") || swatch.value;
            var stringFormat = (swatch.count > 1) ? Strings.COLOR_EDITOR_USED_COLOR_TIP_PLURAL : Strings.COLOR_EDITOR_USED_COLOR_TIP_SINGULAR,
                usedColorTip = StringUtils.format(stringFormat, swatch.value, swatch.count);

            self.$swatches.append("

 " + swatch.value + "
");
        });

        // Add key & click listeners to each
        this.$swatches.find("li").keydown(function (event) {
            if (event.keyCode === KeyEvent.DOM_VK_RETURN ||
                    event.keyCode === KeyEvent.DOM_VK_ENTER ||
                    event.keyCode === KeyEvent.DOM_VK_SPACE) {
                // Enter/Space is same as clicking on swatch

                self._commitColor($(event.currentTarget).find(".value").html());
            } else if (event.keyCode === KeyEvent.DOM_VK_TAB) {
                // Tab on last swatch loops back to color square
                if (!event.shiftKey && $(this).next("li").length === 0) {
                    self.$selectionBase.focus();
                    return false;
                }
            }
        });

        this.$swatches.find("li").click(function (event) {
            self._commitColor($(event.currentTarget).find(".value").html());
        });
    };

    /**
     * Checks whether colorVal is a valid color
     * @param {!string} colorVal
     * @return {boolean} Whether colorVal is valid
     */
    ColorEditor.prototype.isValidColor = function (colorVal) {
        return tinycolor(colorVal).isValid();
    };

    /**
     * Sets _hsv and _color based on an HSV input, and updates the UI. Attempts to preserve
     * the previous color format.
     * @param {!{h:number=, s:number=, v:number=}} hsv  Any missing values use the previous color's values.
     */
    ColorEditor.prototype.setColorAsHsv = function (hsv) {
        var colorVal, newColor,
            oldFormat = tinycolor(this.getColor()).getFormat();

        // Set our state to the new color
        $.extend(this._hsv, hsv);
        newColor = tinycolor(this._hsv);

        switch (oldFormat) {
        case "hsl":
            colorVal = newColor.toHslString();
            break;
        case "rgb":
            colorVal = newColor.toRgbString();
            break;
        case "prgb":
            colorVal = newColor.toPercentageRgbString();
            break;
        case "hex":
        case "name":
            colorVal = this._hsv.a < 1 ? newColor.toRgbString() : newColor.toHexString();
            break;
        case "0x":
            colorVal = as0xString(newColor);
            break;
        }
        colorVal = this._isUpperCase ? colorVal.toUpperCase() : colorVal;
        this._commitColor(colorVal, false);
    };

    /**
     * Sets _color (and optionally _hsv) based on a string input, and updates the UI. The string's
     * format determines the new selected color's format.
     * @param {!string} colorVal
     * @param {boolean=} resetHsv  Pass false ONLY if hsv set already been modified to match colorVal. Default: true.
     */
    ColorEditor.prototype._commitColor = function (colorVal, resetHsv) {

        if (resetHsv === undefined) {
            resetHsv = true;
        }
        this._callback(colorVal);

        var colorObj = checkSetFormat(colorVal);
        colorObj._originalInput = colorVal;
        this._color = colorObj;

        if (resetHsv) {
            this._hsv = this._color.toHsv();
        }

        this._redoColor = null;  // if we had undone, this new value blows away the redo history
        this._synchronize();
    };

    /**
     * Sets _color and _hsv based on a string input, and updates the UI. The string's
     * format determines the new selected color's format.
     * @param {!string} colorVal
     */
    ColorEditor.prototype.setColorFromString = function (colorVal) {
        this._commitColor(colorVal, true);  // TODO (#2204): make this less entangled with setColorAsHsv()
    };

    /** Converts a mouse coordinate to be relative to zeroPos, and clips to [0, maxOffset] */
    function _getNewOffset(pos, zeroPos, maxOffset) {
        var offset = pos - zeroPos;
        offset = Math.min(maxOffset, Math.max(0, offset));
        return offset;
    }

    /** Dragging color square's thumb */
    ColorEditor.prototype._handleSelectionFieldDrag = function (event) {
        var height  = this.$selection.height(),
            width   = this.$selection.width(),
            xOffset = _getNewOffset(event.clientX, this.$selection.offset().left, width),
            yOffset = _getNewOffset(event.clientY, this.$selection.offset().top, height),
            hsv     = {};
        hsv.s = xOffset / width;
        hsv.v = 1 - yOffset / height;
        this.setColorAsHsv(hsv, false);
        if (!this.$selection.find(".selector-base").is(":focus")) {
            this.$selection.find(".selector-base").focus();
        }
    };

    /** Dragging hue slider thumb */
    ColorEditor.prototype._handleHueDrag = function (event) {
        var height = this.$hueSlider.height(),
            offset = _getNewOffset(event.clientY, this.$hueSlider.offset().top, height),
            hsv    = {};
        hsv.h = (1 - offset / height) * 360;
        this.setColorAsHsv(hsv, false);
        if (!this.$hueSlider.find(".selector-base").is(":focus")) {
            this.$hueSlider.find(".selector-base").focus();
        }
    };

    /** Dragging opacity slider thumb */
    ColorEditor.prototype._handleOpacityDrag = function (event) {
        var height = this.$opacitySlider.height(),
            offset = _getNewOffset(event.clientY, this.$opacitySlider.offset().top, height),
            hsv    = {};
        hsv.a = 1 - (offset / height);
        this.setColorAsHsv(hsv, false);
        if (!this.$opacitySlider.find(".selector-base").is(":focus")) {
            this.$opacitySlider.find(".selector-base").focus();
        }
    };

    /**
     * Helper for attaching drag-related mouse listeners to an element. It's up to
     * 'handler' to actually move the element as mouse is dragged.
     * @param {!function(jQuery.event)} handler  Called whenever drag position changes
     */
    ColorEditor.prototype._registerDragHandler = function ($element, handler) {
        var mouseupHandler = function (event) {
            $(window).unbind("mousemove", handler);
            $(window).unbind("mouseup", mouseupHandler);
        };
        $element.mousedown(function (event) {
            $(window).bind("mousemove", handler);
            $(window).bind("mouseup", mouseupHandler);
        });
        $element.mousedown(handler);  // run drag-update handler on initial mousedown too
    };

    /**
     * Handles undo gestures while color picker has focus. We don't want to let CodeMirror's
     * usual undo logic run since it will destroy our marker.
     */
    ColorEditor.prototype.undo = function () {
        if (this._originalColor.toString() !== this._color.toString()) {
            var curColor = this._color.toString();
            this._commitColor(this._originalColor, true);
            this._redoColor = curColor;
        }
    };

    /** Similarly, handle redo gestures while color picker has focus. */
    ColorEditor.prototype.redo = function () {
        if (this._redoColor) {
            this._commitColor(this._redoColor, true);
            this._redoColor = null;
        }
    };

    /**
     * Global handler for keys in the color editor. Catches undo/redo keys and traps
     * arrow keys that would be handled by the scroller.
     */
    ColorEditor.prototype._handleKeydown = function (event) {
        var hasCtrl = (brackets.platform === "win") ? (event.ctrlKey) : (event.metaKey);
        if (hasCtrl) {
            switch (event.keyCode) {
            case KeyEvent.DOM_VK_Z:
                if (event.shiftKey) {
                    this.redo();
                } else {
                    this.undo();
                }
                return false;
            case KeyEvent.DOM_VK_Y:
                this.redo();
                return false;
            }
        } else {
            if (event.keyCode === KeyEvent.DOM_VK_LEFT ||
                    event.keyCode === KeyEvent.DOM_VK_RIGHT ||
                    event.keyCode === KeyEvent.DOM_VK_UP ||
                    event.keyCode === KeyEvent.DOM_VK_DOWN) {
                // Prevent arrow keys that weren't handled by a child control
                // from being handled by a parent, either through bubbling or
                // through default native behavior. There isn't a good general
                // way to tell if the target would handle this event by default,
                // so we look to see if the target is a text input control.
                var preventDefault = false,
                    $target = $(event.target);

                // If the input has no "type" attribute, it defaults to text. So we
                // have to check for both possibilities.
                if ($target.is("input:not([type])") || $target.is("input[type=text]")) {
                    // Text input control. In WebKit, if the cursor gets to the start
                    // or end of a text field and can't move any further, the default
                    // action doesn't take place in the text field, so the event is handled
                    // by the outer scroller. We have to prevent in that case too.
                    if ($target[0].selectionStart === $target[0].selectionEnd &&
                            ((event.keyCode === KeyEvent.DOM_VK_LEFT && $target[0].selectionStart === 0) ||
                             (event.keyCode === KeyEvent.DOM_VK_RIGHT && $target[0].selectionEnd === $target.val().length))) {
                        preventDefault = true;
                    }
                } else {
                    // Not a text input control, so we want to prevent default.
                    preventDefault = true;
                }

                if (preventDefault) {
                    event.stopPropagation();
                    return false; // equivalent to event.preventDefault()
                }
            }
        }
    };

    ColorEditor.prototype._handleHslKeydown = function (event) {
        if (event.keyCode === KeyEvent.DOM_VK_TAB) {
            // If we're the last focusable element (no color swatches), Tab wraps around to color square
            if (!event.shiftKey) {
                if (this.$swatches.children().length === 0) {
                    this.$selectionBase.focus();
                    return false;
                }
            }
        }
    };

    /** Key events on the color square's thumb */
    ColorEditor.prototype._handleSelectionKeydown = function (event) {
        var hsv = {},
            step = 1.5,
            xOffset,
            yOffset,
            adjustedOffset;

        switch (event.keyCode) {
        case KeyEvent.DOM_VK_LEFT:
        case KeyEvent.DOM_VK_RIGHT:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            xOffset = Number($.trim(this.$selectionBase[0].style.left.replace("%", "")));
            adjustedOffset = (event.keyCode === KeyEvent.DOM_VK_LEFT) ? (xOffset - step) : (xOffset + step);
            xOffset = Math.min(100, Math.max(0, adjustedOffset));
            hsv.s = xOffset / 100;
            this.setColorAsHsv(hsv, false);
            return false;
        case KeyEvent.DOM_VK_DOWN:
        case KeyEvent.DOM_VK_UP:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            yOffset = Number($.trim(this.$selectionBase[0].style.bottom.replace("%", "")));
            adjustedOffset = (event.keyCode === KeyEvent.DOM_VK_DOWN) ? (yOffset - step) : (yOffset + step);
            yOffset = Math.min(100, Math.max(0, adjustedOffset));
            hsv.v = yOffset / 100;
            this.setColorAsHsv(hsv, false);
            return false;
        case KeyEvent.DOM_VK_TAB:
            // Shift+Tab loops back to last focusable element: last swatch if any; format button bar if not
            if (event.shiftKey) {
                if (this.$swatches.children().length === 0) {
                    this.$hslButton.focus();
                } else {
                    this.$swatches.find("li:last").focus();
                }
                return false;
            }
            break;
        }
    };

    /** Key events on the hue slider thumb */
    ColorEditor.prototype._handleHueKeydown = function (event) {
        var hsv = {},
            hue = Number(this._hsv.h),
            step = 3.6;

        switch (event.keyCode) {
        case KeyEvent.DOM_VK_DOWN:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            hsv.h = (hue - step) <= 0 ? 360 - step : hue - step;
            this.setColorAsHsv(hsv, false);
            return false;
        case KeyEvent.DOM_VK_UP:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            hsv.h = (hue + step) >= 360 ? step : hue + step;
            this.setColorAsHsv(hsv, false);
            return false;
        }
    };

    /** Key events on the opacity slider thumb */
    ColorEditor.prototype._handleOpacityKeydown = function (event) {
        var alpha = this._hsv.a,
            hsv = {},
            step = 0.01;

        switch (event.keyCode) {
        case KeyEvent.DOM_VK_DOWN:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            if (alpha > 0) {
                hsv.a = (alpha - step) <= 0 ? 0 : alpha - step;
                this.setColorAsHsv(hsv);
            }
            return false;
        case KeyEvent.DOM_VK_UP:
            step = event.shiftKey ? step * STEP_MULTIPLIER : step;
            if (alpha < 100) {
                hsv.a = (alpha + step) >= 1 ? 1 : alpha + step;
                this.setColorAsHsv(hsv);
            }
            return false;
        }
    };

    ColorEditor.prototype._bindKeyHandler = function ($element, handler) {
        $element.bind("keydown", handler);
    };

    // Prevent clicks on some UI elements (color selection field, slider and large swatch) from taking focus
    $(window.document).on("mousedown", ".color-selection-field, .slider, .large-swatch", function (e) {
        e.preventDefault();
    });

    exports.ColorEditor = ColorEditor;
});


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

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

    var TERN_ADD_FILES_MSG          = "AddFiles",
        TERN_UPDATE_FILE_MSG        = "UpdateFile",
        TERN_INIT_MSG               = "Init",
        TERN_JUMPTODEF_MSG          = "JumptoDef",
        TERN_COMPLETIONS_MSG        = "Completions",
        TERN_GET_FILE_MSG           = "GetFile",
        TERN_CALLED_FUNC_TYPE_MSG   = "FunctionType",
        TERN_PRIME_PUMP_MSG         = "PrimePump",
        TERN_GET_GUESSES_MSG        = "GetGuesses",
        TERN_WORKER_READY           = "WorkerReady",
        TERN_INFERENCE_TIMEDOUT     = "InferenceTimedOut",
        SET_CONFIG                  = "SetConfig";

    // Message parameter constants
    var TERN_FILE_INFO_TYPE_PART    = "part",
        TERN_FILE_INFO_TYPE_FULL    = "full",
        TERN_FILE_INFO_TYPE_EMPTY   = "empty";

    exports.TERN_ADD_FILES_MSG          = TERN_ADD_FILES_MSG;
    exports.TERN_JUMPTODEF_MSG          = TERN_JUMPTODEF_MSG;
    exports.TERN_COMPLETIONS_MSG        = TERN_COMPLETIONS_MSG;
    exports.TERN_INIT_MSG               = TERN_INIT_MSG;
    exports.TERN_GET_FILE_MSG           = TERN_GET_FILE_MSG;
    exports.TERN_CALLED_FUNC_TYPE_MSG   = TERN_CALLED_FUNC_TYPE_MSG;
    exports.TERN_PRIME_PUMP_MSG         = TERN_PRIME_PUMP_MSG;
    exports.TERN_GET_GUESSES_MSG        = TERN_GET_GUESSES_MSG;
    exports.TERN_UPDATE_FILE_MSG        = TERN_UPDATE_FILE_MSG;
    exports.TERN_WORKER_READY           = TERN_WORKER_READY;
    exports.TERN_FILE_INFO_TYPE_PART    = TERN_FILE_INFO_TYPE_PART;
    exports.TERN_FILE_INFO_TYPE_FULL    = TERN_FILE_INFO_TYPE_FULL;
    exports.TERN_FILE_INFO_TYPE_EMPTY   = TERN_FILE_INFO_TYPE_EMPTY;
    exports.TERN_INFERENCE_TIMEDOUT     = TERN_INFERENCE_TIMEDOUT;
    exports.SET_CONFIG                  = SET_CONFIG;
});


<!DOCTYPE html>

        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>GETTING STARTED WITH BRACKETS</title>
        <meta name="description" content="An interactive getting started guide for Brackets.">
        <link rel="stylesheet" href="main.css">

GETTING STARTED WITH BRACKETS

This is your guide!

            Welcome to Brackets, a modern open-source code editor that understands web design. It's a lightweight,
            yet powerful, code editor that blends visual tools into the editor so you get the right amount of help
            when you want it.

            Brackets is a different type of editor.
            Brackets has some unique features like Quick Edit, Live Preview and others that you may not find in other
            editors. Brackets is written in JavaScript, HTML and CSS. That means that most of you using Brackets
            have the skills necessary to modify and extend the editor. In fact, we use Brackets every day to build
            Brackets. To learn more about how to use the key features, read on.

Projects in Brackets

            In order to edit your own code using Brackets, you can just open the folder containing your files.
            Brackets treats the currently open folder as a "project"; features like Code Hints, Live Preview and
            Quick Edit only use files within the currently open folder.

        <samp>
            Once you're ready to get out of this sample project and edit your own code, you can use the dropdown
            in the left sidebar to switch folders. Right now, the dropdown says "Getting Started" - that's the
            folder containing the file you're looking at right now. Click on the dropdown and choose "Open Folder…"
            to open your own folder.
            You can also use the dropdown later to switch back to folders you've opened previously, including this
            sample project.
        </samp>

Quick Edit for CSS and JavaScript

            No more switching between documents and losing your context. When editing HTML, use the
            Cmd/Ctrl + E shortcut to open a quick inline editor that displays all the related CSS.
            Make a tweak to your CSS, hit ESC and you're back to editing HTML, or just leave the
            CSS rules open and they'll become part of your HTML editor. If you hit ESC outside of
            a quick inline editor, they'll all collapse. Quick Edit will also find rules defined in LESS and
            SCSS files, including nested rules.

        <samp>
            Want to see it in action? Place your cursor on the  tag above and press
            Cmd/Ctrl + E. You should see a CSS quick editor appear above, showing the CSS rule that
            applies to it. Quick Edit works in class and id attributes as well. You can use it with your
            LESS and SCSS files also.

            You can create new rules the same way. Click in one of the  tags above and press
            Cmd/Ctrl + E. There are no rules for it right now, but you can click the New Rule
            button to add a new rule for .
        </samp>

            You can use the same shortcut to edit other things as well - like functions in JavaScript,
            colors, and animation timing functions - and we're adding more and more all the time.

            For now inline editors cannot be nested, so you can only use Quick Edit while the cursor
            is in a "full size" editor.

Preview HTML and CSS changes live in the browser

            You know that "save/reload dance" we've been doing for years? The one where you make changes in
            your editor, hit save, switch to the browser and then refresh to finally see the result?
            With Brackets, you don't have to do that dance.

            Brackets will open a live connection to your local browser and push HTML and CSS updates as you
            type! You might already be doing something like this today with browser-based tools, but with Brackets
            there is no need to copy and paste the final code back into the editor. Your code runs in the
            browser, but lives in your editor!

Live Highlight HTML elements and CSS rules

            Brackets makes it easy to see how your changes in HTML and CSS will affect the page. When your cursor
            is on a CSS rule, Brackets will highlight all affected elements in the browser. Similarly, when editing
            an HTML file, Brackets will highlight the corresponding HTML elements in the browser.

        <samp>
            If you have Google Chrome installed, you can try this out yourself. Click on the lightning bolt
            icon in the top right corner of your Brackets window or hit Cmd/Ctrl + Alt + P. When
            Live Preview is enabled on an HTML document, all linked CSS documents can be edited in real-time.
            The icon will change from gray to gold when Brackets establishes a connection to your browser.

            Now, place your cursor on the  tag above. Notice the blue highlight that appears
            around the image in Chrome. Next, use Cmd/Ctrl + E to open up the defined CSS rules.
            Try changing the size of the border from 10px to 20px or change the background
            color from "transparent" to "hotpink". If you have Brackets and your browser running side-by-side, you
            will see your changes instantly reflected in your browser. Cool, right?
        </samp>

            Today, Brackets only supports Live Preview for HTML and CSS. However, in the current version, changes to
            JavaScript files are automatically reloaded when you save. We are currently working on Live Preview
            support for JavaScript. Live previews are also only possible with Google Chrome, but we hope
            to bring this functionality to all major browsers in the future.

Quick View

            For those of us who haven't yet memorized the color equivalents for HEX or RGB values, Brackets makes
            it quick and easy to see exactly what color is being used. In either CSS or HTML, simply hover over any
            color value or gradient and Brackets will display a preview of that color/gradient automatically. The
            same goes for images: simply hover over the image link in the Brackets editor and it will display a
            thumbnail preview of that image.

        <samp>
            To try out Quick View for yourself, place your cursor on the  tag at the top of this
            document and press Cmd/Ctrl + E to open a CSS quick editor. Now simply hover over any of the
            color values within the CSS. You can also see it in action on gradients by opening a CSS quick editor
            on the  tag and hovering over any of the background image values. To try out the image
            preview, place your cursor over the screenshot image included earlier in this document.
        </samp>

Need something else? Try an extension!

            In addition to all the goodness that's built into Brackets, our large and growing community of
            extension developers has built hundreds of extensions that add useful functionality. If there's
            something you need that Brackets doesn't offer, more than likely someone has built an extension for
            it. To browse or search the list of available extensions, choose File > Extension
            Manager… and click on the "Available" tab. When you find an extension you want, just click
            the "Install" button next to it.

Get involved

            Brackets is an open-source project. Web developers from around the world are contributing to build
            a better code editor. Many more are building extensions that expand the capabilities of Brackets.
            Let us know what you think, share your ideas or contribute directly to the project.

Brackets.io

Brackets Team Blog

Brackets on GitHub

Brackets Extension Registry

Brackets Wiki

Brackets Developer Mailing List

@brackets on Twitter

Chat with Brackets developers on IRC in #brackets on Freenode


<!doctype html>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Foo</title>
<link rel="stylesheet" href="css/foo.css">

Foo

Intro to foo

foo

bar

baz

It's all about the bar


var swatchValue = swatch.value.replace("0x","#") || swatch.value;

var stringFormat = (swatch.count > 1) ? Strings.COLOR_EDITOR_USED_COLOR_TIP_PLURAL : Strings.COLOR_EDITOR_USED_COLOR_TIP_SINGULAR,

usedColorTip = StringUtils.format(stringFormat, swatch.value, swatch.count);

self.$swatches.append("

<div class='swatch' style='background-color: " +

swatchValue + ";' title='" + usedColorTip + "'>

 <span class='value'" + " title='" +

keep keep keep keep replace keep keep keep keep keep

var swatchValue = checkSetFormat(swatch.value, true);

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

/*jslint regexp: true */

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

    // Brackets modules
    var ColorUtils          = brackets.getModule("utils/ColorUtils"),
        CommandManager      = brackets.getModule("command/CommandManager"),
        Commands            = brackets.getModule("command/Commands"),
        CSSUtils            = brackets.getModule("language/CSSUtils"),
        EditorManager       = brackets.getModule("editor/EditorManager"),
        ExtensionUtils      = brackets.getModule("utils/ExtensionUtils"),
        FileUtils           = brackets.getModule("file/FileUtils"),
        Menus               = brackets.getModule("command/Menus"),
        PreferencesManager  = brackets.getModule("preferences/PreferencesManager"),
        LanguageManager     = brackets.getModule("language/LanguageManager"),
        Strings             = brackets.getModule("strings"),
        ViewUtils           = brackets.getModule("utils/ViewUtils"),
        TokenUtils          = brackets.getModule("utils/TokenUtils"),
        PathUtils           = brackets.getModule("thirdparty/path-utils/path-utils");

    var previewContainerHTML       = require("text!QuickViewTemplate.html");

    var enabled,                             // Only show preview if true
        prefs                      = null,   // Preferences
        $previewContainer,                   // Preview container
        $previewContent,                     // Preview content holder
        lastMousePos,                        // Last mouse position
        animationRequest,                    // Request for animation frame
        extensionlessImagePreview;           // Whether to try and preview extensionless URLs

    // Constants
    var CMD_ENABLE_QUICK_VIEW       = "view.enableQuickView",
        HOVER_DELAY                 = 350,  // Time (ms) mouse must remain over a provider's matched text before popover appears
        POINTER_HEIGHT              = 15,   // Pointer height, used to shift popover above pointer (plus a little bit of space)
        POPOVER_HORZ_MARGIN         =  5;   // Horizontal margin

    var styleLanguages = ["css", "text/x-less", "sass", "text/x-scss", "stylus"];

    // List of protocols which we will support for image preview urls
    var validProtocols = ["data:", "http:", "https:", "ftp:", "file:"];

    prefs = PreferencesManager.getExtensionPrefs("quickview");
    prefs.definePreference("enabled", "boolean", true, {
        description: Strings.DESCRIPTION_QUICK_VIEW_ENABLED
    });

    // Whether or not to try and show image previews for URLs missing extensions
    // (e.g., https://avatars2.githubusercontent.com/u/476009?v=3&s=200)
    prefs.definePreference("extensionlessImagePreview", "boolean", true, {
        description: Strings.DESCRIPTION_EXTENSION_LESS_IMAGE_PREVIEW
    });

    /**
     * There are three states for this var:
     * 1. If null, there is no provider result for the given mouse position.
     * 2. If non-null, and visible==true, there is a popover currently showing.
     * 3. If non-null, but visible==false, we're waiting for HOVER_DELAY, which
     *    is tracked by hoverTimer. The state changes to visible==true as soon as
     *    there is a provider. If the mouse moves before then, timer is restarted.
     *
     * @type {{
     *      visible: boolean,
     *      editor: !Editor,
     *      hoverTimer: number,             - setTimeout() token
     *      start: !{line, ch},             - start of matched text range
     *      end: !{line, ch},               - end of matched text range
     *      content: !string,               - HTML content to display in popover
     *      onShow: ?function():void,       - called once popover content added to the DOM (may never be called)
     *        - if specified, must call positionPreview()
     *      xpos: number,                   - x of center of popover
     *      ytop: number,                   - y of top of matched text (when popover placed above text, normally)
     *      ybot: number,                   - y of bottom of matched text (when popover moved below text, avoiding window top)
     *      marker: ?CodeMirror.TextMarker  - only set once visible==true
     * }}
     */
    var popoverState = null;

    // Popover widget management ----------------------------------------------

    /**
     * Cancels whatever popoverState was currently pending and sets it back to null. If the popover was visible,
     * hides it; if the popover was invisible and still pending, cancels hoverTimer so it will never be shown.
     */
    function hidePreview() {
        if (!popoverState) {
            return;
        }

        if (popoverState.visible) {
            popoverState.marker.clear();

            $previewContent.empty();
            $previewContainer.hide();
            $previewContainer.removeClass("active");
        } else {
            window.clearTimeout(popoverState.hoverTimer);
        }
        popoverState = null;
    }

    function positionPreview(editor, xpos, ypos, ybot) {
        var previewWidth  = $previewContainer.outerWidth(),
            top           = ypos - $previewContainer.outerHeight() - POINTER_HEIGHT,
            left          = xpos - previewWidth / 2,
            elementRect = {
                top:    top,
                left:   left - POPOVER_HORZ_MARGIN,
                height: $previewContainer.outerHeight() + POINTER_HEIGHT,
                width:  previewWidth + 2 * POPOVER_HORZ_MARGIN
            },
            clip = ViewUtils.getElementClipSize($(editor.getRootElement()), elementRect);

        // Prevent horizontal clipping
        if (clip.left > 0) {
            left += clip.left;
        } else if (clip.right > 0) {
            left -= clip.right;
        }

        // If clipped on top, flip popover below line
        if (clip.top > 0) {
            top = ybot + POINTER_HEIGHT;
            $previewContainer
                .removeClass("preview-bubble-above")
                .addClass("preview-bubble-below");
        } else {
            $previewContainer
                .removeClass("preview-bubble-below")
                .addClass("preview-bubble-above");
        }

        $previewContainer
            .css({
                left: left,
                top: top
            })
            .addClass("active");
    }

    function divContainsMouse($div, mousePos) {
        var offset = $div.offset();

        return (mousePos.clientX >= offset.left &&
                mousePos.clientX <= offset.left + $div.width() &&
                mousePos.clientY >= offset.top &&
                mousePos.clientY <= offset.top + $div.height());
    }

    // Color & gradient preview provider --------------------------------------

    function colorAndGradientPreviewProvider(editor, pos, token, line) {

        // Check for gradient. -webkit-gradient() can have parens in parameters
        // nested 2 levels. Other gradients can only nest 1 level.
        var gradientRegEx = /-webkit-gradient\((?:[^\(]*?(?:\((?:[^\(]*?(?:\([^\)]*?\))*?)*?\))*?)*?\)|(?:(?:-moz-|-ms-|-o-|-webkit-|:|\s)((repeating-)?linear-gradient)|(?:-moz-|-ms-|-o-|-webkit-|:|\s)((repeating-)?radial-gradient))(\((?:[^\)]*?(?:\([^\)]*?\))*?)*?\))/gi,
            colorRegEx    = new RegExp(ColorUtils.COLOR_REGEX),
            mode          = TokenUtils.getModeAt(editor._codeMirror, pos, false),
            isStyleSheet  = (styleLanguages.indexOf(mode) !== -1);

        function areParensBalanced(str) {
            var i,
                nestLevel = 0,
                len;

            if (isStyleSheet) {
                // Remove comments & strings from style sheets
                str = CSSUtils.reduceStyleSheetForRegExParsing(str);
            }
            len = str.length;

            for (i = 0; i < len; i++) {
                switch (str[i]) {
                case "(":
                    nestLevel++;
                    break;
                case ")":
                    nestLevel--;
                    break;
                case "\\":
                    i++;    // next char is escaped, so skip it
                    break;
                }
            }

            // if parens are balanced, nest level will be 0
            return (nestLevel === 0);
        }

        function execGradientMatch(line, parensBalanced) {
            // Unbalanced parens cause infinite loop (see issue #4650)
            var gradientMatch = (parensBalanced ? gradientRegEx.exec(line) : null),
                prefix = "",
                colorValue;

            if (gradientMatch) {
                if (gradientMatch[0].indexOf("@") !== -1) {
                    // If the gradient match has "@" in it, it is most likely a less or
                    // sass variable. Ignore it since it won't be displayed correctly.
                    gradientMatch = null;

                } else {
                    // If it was a linear-gradient or radial-gradient variant with a vendor prefix
                    // add "-webkit-" so it shows up correctly in Brackets.
                    if (gradientMatch[0].match(/-o-|-moz-|-ms-|-webkit-/i)) {
                        prefix = "-webkit-";
                    }

                    // For prefixed gradients, use the non-prefixed value as the color value.
                    // "-webkit-" will be added before this value later
                    if (gradientMatch[1]) {
                        colorValue = gradientMatch[1] + gradientMatch[5];    // linear gradiant
                    } else if (gradientMatch[3]) {
                        colorValue = gradientMatch[3] + gradientMatch[5];    // radial gradiant
                    } else if (gradientMatch[0]) {
                        colorValue = gradientMatch[0];                       // -webkit-gradient
                        prefix = "";                                         // do not prefix
                    }
                }
            }

            return {
                match:      gradientMatch,
                prefix:     prefix,
                colorValue: colorValue
            };
        }

        function execColorMatch(editor, line, pos) {
            var colorMatch,
                ignoreNamedColors;

            function hyphenOnMatchBoundary(match, line) {
                var beforeIndex, afterIndex;
                if (match) {
                    beforeIndex = match.index - 1;
                    if (beforeIndex >= 0 && line[beforeIndex] === "-") {
                        return true;
                    } else {
                        afterIndex = match.index + match[0].length;
                        if (afterIndex < line.length && line[afterIndex] === "-") {
                            return true;
                        }
                    }
                }

                return false;
            }
            function isNamedColor(match) {
                if (match && match[0] && /^[a-z]+$/i.test(match[0])) { // only for color names, not for hex-/rgb-values
                    return true;
                }
            }

            // Hyphens do not count as a regex word boundary (\b), so check for those here
            do {
                colorMatch = colorRegEx.exec(line);
                if (!colorMatch) {
                    break;
                }
                if (ignoreNamedColors === undefined) {
                    var mode = TokenUtils.getModeAt(editor._codeMirror, pos, false).name;
                    ignoreNamedColors = styleLanguages.indexOf(mode) === -1;
                }
            } while (hyphenOnMatchBoundary(colorMatch, line) ||
                    (ignoreNamedColors && isNamedColor(colorMatch)));

            return colorMatch;
        }

        // simple css property splitter (used to find color stop arguments in gradients)
        function splitStyleProperty(property) {
            var token = /((?:[^"']|".*?"|'.*?')*?)([(,)]|$)/g;
            var recurse = function () {
                var array = [];
                for (;;) {
                    var result = token.exec(property);
                    if (result[2] === "(") {
                        var str = result[1].trim() + "(" + recurse().join(",") + ")";
                        result = token.exec(property);
                        str += result[1];
                        array.push(str);
                    } else {
                        array.push(result[1].trim());
                    }
                    if (result[2] !== ",") {
                        return array;
                    }
                }
            };
            return (recurse());
        }

        // color stop helpers
        function isGradientColorStop(args) {
            return (args.length > 0 && args[0].match(colorRegEx) !== null);
        }

        function hasLengthInPixels(args) {
            return (args.length > 1 && args[1].indexOf("px") > 0);
        }

        // Normalizes px color stops to %
        function normalizeGradientExpressionForQuickview(expression) {
            if (expression.indexOf("px") > 0) {
                var paramStart = expression.indexOf("(") + 1,
                    paramEnd = expression.lastIndexOf(")"),
                    parameters = expression.substring(paramStart, paramEnd),
                    params = splitStyleProperty(parameters),
                    lowerBound = 0,
                    upperBound = $previewContainer.width(),
                    args,
                    thisSize,
                    i;

                // find lower bound
                for (i = 0; i < params.length; i++) {
                    args = params[i].split(" ");

                    if (hasLengthInPixels(args)) {
                        thisSize = parseFloat(args[1]);

                        upperBound = Math.max(upperBound, thisSize);
                        // we really only care about converting negative
                        //  pixel values -- so take the smallest negative pixel
                        //  value and use that as baseline for display purposes
                        if (thisSize < 0) {
                            lowerBound = Math.min(lowerBound, thisSize);
                        }
                    }
                }

                // convert negative lower bound to positive and adjust all pixel values
                //  so that -20px is now 0px and 100px is now 120px
                lowerBound = Math.abs(lowerBound);

                // Offset the upperbound by the lowerBound to give us a corrected context
                upperBound += lowerBound;

                // convert to %
                for (i = 0; i < params.length; i++) {
                    args = params[i].split(" ");
                    if (isGradientColorStop(args) && hasLengthInPixels(args)) {
                        if (upperBound === 0) {
                            thisSize = 0;
                        } else {
                            thisSize = ((parseFloat(args[1]) + lowerBound) / upperBound) * 100;
                        }
                        args[1] = thisSize + "%";
                    }
                    params[i] = args.join(" ");
                }

                // put it back together.
                expression = expression.substring(0, paramStart) + params.join(", ") + expression.substring(paramEnd);
            }
            return expression;
        }

        var parensBalanced = areParensBalanced(line),
            gradientMatch = execGradientMatch(line, parensBalanced),
            match = gradientMatch.match || execColorMatch(editor, line, pos),
            cm = editor._codeMirror;

        while (match) {
            if (pos.ch < match.index) {
                // Gradients are matched first, then colors, so...
                if (gradientMatch.match) {
                    // ... gradient match is past cursor -- stop looking for gradients, start searching for colors
                    gradientMatch = { match: null, prefix: "", colorValue: null };
                } else {
                    // ... color match is past cursor -- stop looping
                    break;
                }
            } else if (pos.ch <= match.index + match[0].length) {
                // build the css for previewing the gradient from the regex result
                var previewCSS = gradientMatch.prefix + (gradientMatch.colorValue || match[0]);

                if ((/^0x/).test(previewCSS)) {
                   previewCSS = previewCSS.replace("0x","#");
                };

                // normalize the arguments to something that we can display to the user
                // NOTE: we need both the div and the popover's _previewCSS member
                //          (used by unit tests) to match so normalize the css for both
                previewCSS = normalizeGradientExpressionForQuickview(previewCSS);

                var preview = "
" + "
";
                var startPos = {line: pos.line, ch: match.index},
                    endPos = {line: pos.line, ch: match.index + match[0].length},
                    startCoords = cm.charCoords(startPos),
                    xPos;

                xPos = (cm.charCoords(endPos).left - startCoords.left) / 2 + startCoords.left;

                return {
                    start: startPos,
                    end: endPos,
                    content: preview,
                    xpos: xPos,
                    ytop: startCoords.top,
                    ybot: startCoords.bottom,
                    _previewCSS: previewCSS
                };
            }

            // Get next match
            if (gradientMatch.match) {
                gradientMatch = execGradientMatch(line, parensBalanced);
            }
            match = gradientMatch.match || execColorMatch(editor, line, pos);
        }

        return null;
    }

    // Image preview provider -------------------------------------------------

    function imagePreviewProvider(editor, pos, token, line) {
        var cm = editor._codeMirror;

        // Check for image name
        var urlRegEx = /url\(([^\)]*)\)/gi,
            tokenString,
            urlMatch;

        if (token.type === "string") {
            tokenString = token.string;
        } else {
            urlMatch = urlRegEx.exec(line);
            while (urlMatch) {
                if (pos.ch < urlMatch.index) {
                    // match is past cursor, so stop looping
                    break;
                } else if (pos.ch <= urlMatch.index + urlMatch[0].length) {
                    tokenString = urlMatch[1];
                    break;
                }
                urlMatch = urlRegEx.exec(line);
            }
        }

        if (!tokenString) {
            return null;
        }

        // Strip leading/trailing quotes, if present
        tokenString = tokenString.replace(/(^['"])|(['"]$)/g, "");

        var sPos, ePos;
        var docPath = editor.document.file.fullPath;
        var imgPath;

        // Determine whether or not this URL/path is likely to be an image.
        var parsed = PathUtils.parseUrl(tokenString);
        // If the URL has a protocol, check if it's one of the supported protocols
        var hasProtocol = parsed.protocol !== "" && validProtocols.indexOf(parsed.protocol.trim().toLowerCase()) !== -1;
        var ext = parsed.filenameExtension.replace(/^\./, '');
        var language = LanguageManager.getLanguageForExtension(ext);
        var id = language && language.getId();
        var isImage = id === "image" || id === "svg";

        // Use this URL if this is an absolute URL and either points to a
        // filename with a known image extension, or lacks an extension (e.g.,
        // a web service that returns an image). Honour the extensionlessImagePreview
        // preference as well in the latter case.
        if (hasProtocol && (isImage || (!ext && extensionlessImagePreview))) {
            imgPath = tokenString;
        }
        // Use this filename if this is a path with a known image extension.
        else if (!hasProtocol && isImage) {
            imgPath = "file:///" + FileUtils.getDirectoryPath(docPath) + tokenString;
        }

        if (!imgPath) {
            return null;
        }

        if (urlMatch) {
            sPos = {line: pos.line, ch: urlMatch.index};
            ePos = {line: pos.line, ch: urlMatch.index + urlMatch[0].length};
        } else {
            sPos = {line: pos.line, ch: token.start};
            ePos = {line: pos.line, ch: token.end};
        }

        var imgPreview = "
"          +
                         "    "    +
                         "
";
        var coord = cm.charCoords(sPos);
        var xpos = (cm.charCoords(ePos).left - coord.left) / 2 + coord.left;

        var showHandler = function () {
            // Hide the preview container until the image is loaded.
            $previewContainer.hide();

            $previewContainer.find(".image-preview > img").on("load", function () {
                $previewContent
                    .append("
" +
                                this.naturalWidth + " × " + this.naturalHeight + " " + Strings.UNIT_PIXELS +
                            "
"
                        );
                $previewContainer.show();
                positionPreview(editor, popoverState.xpos, popoverState.ytop, popoverState.ybot);
            }).on("error", function (e) {
                e.preventDefault();
                hidePreview();
            });
        };

        return {
            start: sPos,
            end: ePos,
            content: imgPreview,
            onShow: showHandler,
            xpos: xpos,
            ytop: coord.top,
            ybot: coord.bottom,
            _imgPath: imgPath
        };
    }

    // Preview hide/show logic ------------------------------------------------

    /**
     * Returns a 'ready for use' popover state object:
     * { visible: false, editor, start, end, content, ?onShow, xpos, ytop, ybot }
     * Lacks only hoverTimer (supplied by handleMouseMove()) and marker (supplied by showPreview()).
     */
    function queryPreviewProviders(editor, pos, token) {
        var line = editor.document.getLine(pos.line);

        // FUTURE: Support plugin providers. For now we just hard-code...
        var popover = colorAndGradientPreviewProvider(editor, pos, token, line) ||
                      imagePreviewProvider(editor, pos, token, line);

        if (popover) {
            // Providers return just { start, end, content, ?onShow, xpos, ytop, ybot }
            popover.visible = false;
            popover.editor  = editor;
            return popover;
        }

        return null;
    }

    function getHoveredEditor(mousePos) {
        // Figure out which editor we are over
        var fullEditor = EditorManager.getCurrentFullEditor();

        if (!fullEditor || !mousePos) {
            return;
        }

        // Check for inline Editor instances first
        var inlines = fullEditor.getInlineWidgets(),
            i,
            editor;

        for (i = 0; i < inlines.length; i++) {
            var $inlineEditorRoot = inlines[i].editor && $(inlines[i].editor.getRootElement()), // see MultiRangeInlineEditor
                $otherDiv = inlines[i].$htmlContent;

            if ($inlineEditorRoot && divContainsMouse($inlineEditorRoot, mousePos)) {
                editor = inlines[i].editor;
                break;
            } else if ($otherDiv && divContainsMouse($otherDiv, mousePos)) {
                // Mouse inside unsupported inline editor like Quick Docs or Color Editor
                return;
            }
        }

        // Check main editor
        if (!editor) {
            if (divContainsMouse($(fullEditor.getRootElement()), mousePos)) {
                editor = fullEditor;
            }
        }

        return editor;
    }

    /**
     * Changes the current hidden popoverState to visible, showing it in the UI and highlighting
     * its matching text in the editor.
     */
    function showPreview(editor, popover) {
        var token, cm;

        // Figure out which editor we are over
        if (!editor) {
            editor = getHoveredEditor(lastMousePos);
        }

        if (!editor || !editor._codeMirror) {
            hidePreview();
            return;
        }

        cm = editor._codeMirror;

        // Find char mouse is over
        var pos = cm.coordsChar({left: lastMousePos.clientX, top: lastMousePos.clientY});

        // No preview if mouse is past last char on line
        if (pos.ch >= editor.document.getLine(pos.line).length) {
            return;
        }

        if (popover) {
            popoverState = popover;
        } else {
            // Query providers and append to popoverState
            token = TokenUtils.getTokenAt(cm, pos);
            popoverState = $.extend({}, popoverState, queryPreviewProviders(editor, pos, token));
        }

        if (popoverState && popoverState.start && popoverState.end) {
            popoverState.marker = cm.markText(
                popoverState.start,
                popoverState.end,
                {className: "quick-view-highlight"}
            );

            $previewContent.append(popoverState.content);
            $previewContainer.show();

            popoverState.visible = true;

            if (popoverState.onShow) {
                popoverState.onShow();
            } else {
                positionPreview(editor, popoverState.xpos, popoverState.ytop, popoverState.ybot);
            }
        }
    }

    function processMouseMove() {
        animationRequest = null;

        if (!lastMousePos) {
            return;         // should never get here, but safety first!
        }

        var showImmediately = false,
            editor = null;

        if (popoverState && popoverState.visible) {
            // Only figure out which editor we are over when there is already a popover
            // showing (otherwise wait until after delay to minimize processing)
            editor = getHoveredEditor(lastMousePos);
            if (editor && editor._codeMirror) {
                // Find char mouse is over
                var cm = editor._codeMirror,
                    pos = cm.coordsChar({left: lastMousePos.clientX, top: lastMousePos.clientY});

                if (popoverState.start && popoverState.end &&
                        editor.posWithinRange(pos, popoverState.start, popoverState.end, true) &&
                        (pos.ch < editor.document.getLine(pos.line).length)) {

                    // That one's still relevant - nothing more to do
                    // Note: posWithinRange() includes mouse past end of line, so need to check for that case
                    return;
                }
            }

            // That one doesn't cover this pos - hide it and start anew
            showImmediately = true;
        }

        // Initialize popoverState
        hidePreview();
        popoverState = {};

        // Set timer to scan and show. This will get cancelled (in hidePreview())
        // if mouse movement rendered this popover inapplicable before timer fires.
        // When showing "immediately", still use setTimeout() to make this async
        // so we return from this mousemove event handler ASAP.
        popoverState.hoverTimer = window.setTimeout(function () {
            showPreview(editor);
        }, showImmediately ? 0 : HOVER_DELAY);
    }

    function handleMouseMove(event) {
        lastMousePos = null;

        if (!enabled) {
            return;
        }

        if (event.which) {
            // Button is down - don't show popovers while dragging
            hidePreview();
            return;
        }

        // Keep track of last mouse position
        lastMousePos = {
            clientX: event.clientX,
            clientY: event.clientY
        };

        // Prevent duplicate animation frame requests
        if (!animationRequest) {
            animationRequest = window.requestAnimationFrame(processMouseMove);
        }
    }

    function onActiveEditorChange(event, current, previous) {
        // Hide preview when editor changes
        hidePreview();

        if (previous && previous.document) {
            previous.document.off("change", hidePreview);
        }

        if (current && current.document) {
            current.document.on("change", hidePreview);
        }
    }

    // Menu command handlers
    function updateMenuItemCheckmark() {
        CommandManager.get(CMD_ENABLE_QUICK_VIEW).setChecked(enabled);
    }

    function setExtensionlessImagePreview(_extensionlessImagePreview, doNotSave) {
        if (extensionlessImagePreview !== _extensionlessImagePreview) {
            extensionlessImagePreview = _extensionlessImagePreview;
            if (!doNotSave) {
                prefs.set("extensionlessImagePreview", enabled);
                prefs.save();
            }
        }
    }

    function setEnabled(_enabled, doNotSave) {
        if (enabled !== _enabled) {
            enabled = _enabled;
            var editorHolder = $("#editor-holder")[0];
            if (enabled) {
                // Note: listening to "scroll" also catches text edits, which bubble a scroll event up from the hidden text area. This means
                // we auto-hide on text edit, which is probably actually a good thing.
                editorHolder.addEventListener("mousemove", handleMouseMove, true);
                editorHolder.addEventListener("scroll", hidePreview, true);
                editorHolder.addEventListener("mouseout", hidePreview, true);

                // Setup doc "change" listener
                onActiveEditorChange(null, EditorManager.getActiveEditor(), null);
                EditorManager.on("activeEditorChange", onActiveEditorChange);

            } else {
                editorHolder.removeEventListener("mousemove", handleMouseMove, true);
                editorHolder.removeEventListener("scroll", hidePreview, true);
                editorHolder.removeEventListener("mouseout", hidePreview, true);

                // Cleanup doc "change" listener
                onActiveEditorChange(null, null, EditorManager.getActiveEditor());
                EditorManager.off("activeEditorChange", onActiveEditorChange);

                hidePreview();
            }
            if (!doNotSave) {
                prefs.set("enabled", enabled);
                prefs.save();
            }
        }
        // Always update the checkmark, even if the enabled flag hasn't changed.
        updateMenuItemCheckmark();
    }

    function toggleEnableQuickView() {
        setEnabled(!enabled);
    }

    function _forceShow(popover) {
        hidePreview();
        lastMousePos = {
            clientX: popover.xpos,
            clientY: Math.floor((popover.ybot + popover.ytop) / 2)
        };
        showPreview(popover.editor, popover);
    }

    // Create the preview container
    $previewContainer = $(previewContainerHTML).appendTo($("body"));
    $previewContent = $previewContainer.find(".preview-content");

    // Load our stylesheet
    ExtensionUtils.loadStyleSheet(module, "QuickView.less");

    // Register command
    // Insert menu at specific pos since this may load before OR after code folding extension
    CommandManager.register(Strings.CMD_ENABLE_QUICK_VIEW, CMD_ENABLE_QUICK_VIEW, toggleEnableQuickView);
    Menus.getMenu(Menus.AppMenuBar.VIEW_MENU).addMenuItem(CMD_ENABLE_QUICK_VIEW, null, Menus.AFTER, Commands.VIEW_TOGGLE_INSPECTION);

    // Setup initial UI state
    setEnabled(prefs.get("enabled"), true);
    setExtensionlessImagePreview(prefs.get("extensionlessImagePreview"), true);

    prefs.on("change", "enabled", function () {
        setEnabled(prefs.get("enabled"), true);
    });

    prefs.on("change", "extensionlessImagePreview", function () {
        setExtensionlessImagePreview(prefs.get("extensionlessImagePreview"));
    });

    // For unit testing
    exports._queryPreviewProviders  = queryPreviewProviders;
    exports._forceShow              = _forceShow;
});


Copyright (c) 2010 Caolan McMahon

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


<!DOCTYPE html>

	<meta charset="utf-8">
	<title>jQuery UI Datepicker - Show week of the year</title>
	<link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">

	<link rel="stylesheet" href="../demos.css">

Date: <input type="text" id="datepicker">

The datepicker can show the week of the year. The default calculation follows
	the ISO 8601 definition: the week starts on Monday, the first week of the year
	contains the first Thursday of the year. This means that some days from one
	year may be placed into weeks 'belonging' to another year.


* usual undo logic run since it will destroy our marker.

ColorEditor.prototype.undo = function () {

if (this._originalColor.toString() !== this._color.toString()) {


var assert = require('assert');

var cookie = require('..');

suite('parse');

test('basic', function() {
    assert.deepEqual({ foo: 'bar' }, cookie.parse('foo=bar'));
    assert.deepEqual({ foo: '123' }, cookie.parse('foo=123'));
});

test('ignore spaces', function() {
    assert.deepEqual({ FOO: 'bar', baz: 'raz' },
            cookie.parse('FOO    = bar;   baz  =   raz'));
});

test('escaping', function() {
    assert.deepEqual({ foo: 'bar=123456789&name=Magic+Mouse' },
            cookie.parse('foo="bar=123456789&name=Magic+Mouse"'));

    assert.deepEqual({ email: ' ",;/' },
            cookie.parse('email=%20%22%2c%3b%2f'));
});

test('ignore escaping error and return original value', function() {
    assert.deepEqual({ foo: '%1', bar: 'bar' }, cookie.parse('foo=%1;bar=bar'));
});

test('ignore non values', function() {
    assert.deepEqual({ foo: '%1', bar: 'bar' }, cookie.parse('foo=%1;bar=bar;HttpOnly;Secure'));
});

test('unencoded', function() {
    assert.deepEqual({ foo: 'bar=123456789&name=Magic+Mouse' },
            cookie.parse('foo="bar=123456789&name=Magic+Mouse"',{
                decode: function(value) { return value; }
            }));

    assert.deepEqual({ email: '%20%22%2c%3b%2f' },
            cookie.parse('email=%20%22%2c%3b%2f',{
                decode: function(value) { return value; }
            }));
})


module.exports = function( grunt ) {

var path = require( "path" );

grunt.registerMultiTask( "copy", "Copy files to destination folder and replace @VERSION with pkg.version", function() {
	function replaceVersion( source ) {
		return source.replace( /@VERSION/g, grunt.config( "pkg.version" ) );
	}
	function copyFile( src, dest ) {
		if ( /(js|css)$/.test( src ) ) {
			grunt.file.copy( src, dest, {
				process: replaceVersion
			});
		} else {
			grunt.file.copy( src, dest );
		}
	}
	var files = grunt.file.expandFiles( this.file.src ),
		target = this.file.dest + "/",
		strip = this.data.strip,
		renameCount = 0,
		fileName;
	if ( typeof strip === "string" ) {
		strip = new RegExp( "^" + grunt.template.process( strip, grunt.config() ).replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ) );
	}
	files.forEach(function( fileName ) {
		var targetFile = strip ? fileName.replace( strip, "" ) : fileName;
		copyFile( fileName, target + targetFile );
	});
	grunt.log.writeln( "Copied " + files.length + " files." );
	for ( fileName in this.data.renames ) {
		renameCount += 1;
		copyFile( fileName, target + grunt.template.process( this.data.renames[ fileName ], grunt.config() ) );
	}
	if ( renameCount ) {
		grunt.log.writeln( "Renamed " + renameCount + " files." );
	}
});

grunt.registerMultiTask( "zip", "Create a zip file for release", function() {
	// TODO switch back to adm-zip for better cross-platform compability once it actually works
	// 0.1.3 works, but result can't be unzipped
	// its also a lot slower then zip program, probably due to how its used...
	// var files = grunt.file.expandFiles( "dist/" + this.file.src + "/**/*" );
	// grunt.log.writeln( "Creating zip file " + this.file.dest );

	//var AdmZip = require( "adm-zip" );
	//var zip = new AdmZip();
	//files.forEach(function( file ) {
	//	grunt.verbose.writeln( "Zipping " + file );
	//	// rewrite file names from dist folder (created by build), drop the /dist part
	//	zip.addFile(file.replace(/^dist/, "" ), fs.readFileSync( file ) );
	//});
	//zip.writeZip( "dist/" + this.file.dest );
	//grunt.log.writeln( "Wrote " + files.length + " files to " + this.file.dest );

	var done = this.async(),
		dest = this.file.dest,
		src = grunt.template.process( this.file.src, grunt.config() );
	grunt.utils.spawn({
		cmd: "zip",
		args: [ "-r", dest, src ],
		opts: {
			cwd: 'dist'
		}
	}, function( err, result ) {
		if ( err ) {
			grunt.log.error( err );
			done();
			return;
		}
		grunt.log.writeln( "Zipped " + dest );
		done();
	});
});

grunt.registerMultiTask( "md5", "Create list of md5 hashes for CDN uploads", function() {
	// remove dest file before creating it, to make sure itself is not included
	if ( path.existsSync( this.file.dest ) ) {
		fs.unlinkSync( this.file.dest );
	}
	var crypto = require( "crypto" ),
		dir = this.file.src + "/",
		hashes = [];
	grunt.file.expandFiles( dir + "**/*" ).forEach(function( fileName ) {
		var hash = crypto.createHash( "md5" );
		hash.update( grunt.file.read( fileName, "ascii" ) );
		hashes.push( fileName.replace( dir, "" ) + " " + hash.digest( "hex" ) );
	});
	grunt.file.write( this.file.dest, hashes.join( "\n" ) + "\n" );
	grunt.log.writeln( "Wrote " + this.file.dest + " with " + hashes.length + " hashes" );
});

// only needed for 1.8
grunt.registerTask( "download_docs", function() {
	function capitalize(value) {
		return value[0].toUpperCase() + value.slice(1);
	}
	// should be grunt.config("pkg.version")?
	var version = "1.8",
		docsDir = "dist/docs",
		files = "draggable droppable resizable selectable sortable accordion autocomplete button datepicker dialog progressbar slider tabs position"
		.split(" ").map(function(widget) {
			return {
				url: "http://docs.jquery.com/action/render/UI/API/" + version + "/" + capitalize(widget),
				dest: docsDir + '/' + widget + '.html'
			};
		});
	files = files.concat("animate addClass effect hide removeClass show switchClass toggle toggleClass".split(" ").map(function(widget) {
		return {
			url: "http://docs.jquery.com/action/render/UI/Effects/" + widget,
			dest: docsDir + '/' + widget + '.html'
		};
	}));
	files = files.concat("Blind Clip Drop Explode Fade Fold Puff Slide Scale Bounce Highlight Pulsate Shake Size Transfer".split(" ").map(function(widget) {
		return {
			url: "http://docs.jquery.com/action/render/UI/Effects/" + widget,
			dest: docsDir + '/effect-' + widget.toLowerCase() + '.html'
		};
	}));
	grunt.file.mkdir( "dist/docs" );
	grunt.utils.async.forEach( files, function( file, done ) {
		var out = fs.createWriteStream( file.dest );
		out.on( "close", done );
		request( file.url ).pipe( out );
	}, this.async() );
});

grunt.registerTask( "download_themes", function() {
	// var AdmZip = require('adm-zip');
	var done = this.async(),
		themes = grunt.file.read( "build/themes" ).split(","),
		requests = 0;
	grunt.file.mkdir( "dist/tmp" );
	themes.forEach(function( theme, index ) {
		requests += 1;
		grunt.file.mkdir( "dist/tmp/" + index );
		var zipFileName = "dist/tmp/" + index + ".zip",
			out = fs.createWriteStream( zipFileName );
		out.on( "close", function() {
			grunt.log.writeln( "done downloading " + zipFileName );
			// TODO AdmZip produces "crc32 checksum failed", need to figure out why
			// var zip = new AdmZip(zipFileName);
			// zip.extractAllTo('dist/tmp/' + index + '/');
			// until then, using cli unzip...
			grunt.utils.spawn({
				cmd: "unzip",
				args: [ "-d", "dist/tmp/" + index, zipFileName ]
			}, function( err, result ) {
				grunt.log.writeln( "Unzipped " + zipFileName + ", deleting it now" );
				fs.unlinkSync( zipFileName );
				requests -= 1;
				if (requests === 0) {
					done();
				}
			});
		});
		request( "http://ui-dev.jquery.com/download/?" + theme ).pipe( out );
	});
});

grunt.registerTask( "copy_themes", function() {
	// each package includes the base theme, ignore that
	var filter = /themes\/base/,
		files = grunt.file.expandFiles( "dist/tmp/*/development-bundle/themes/**/*" ).filter(function( file ) {
			return !filter.test( file );
		}),
		// TODO the grunt.template.process call shouldn't be necessary
		target = "dist/" + grunt.template.process( grunt.config( "files.themes" ), grunt.config() ) + "/",
		distFolder = "dist/" + grunt.template.process( grunt.config( "files.dist" ), grunt.config() );
	files.forEach(function( fileName ) {
		var targetFile = fileName.replace( /dist\/tmp\/\d+\/development-bundle\//, "" ).replace( "jquery-ui-.custom", "jquery-ui" );
		grunt.file.copy( fileName, target + targetFile );
	});

	// copy minified base theme from regular release
	files = grunt.file.expandFiles( distFolder + "/themes/base/**/*" );
	files.forEach(function( fileName ) {
		grunt.file.copy( fileName, target + fileName.replace( distFolder, "" ) );
	});
});

grunt.registerTask( "clean", function() {
	require( "rimraf" ).sync( "dist" );
});

grunt.registerTask( "authors", function() {
	var done = this.async();

	grunt.utils.spawn({
		cmd: "git",
		args: [ "log", "--pretty=%an <%ae>" ]
	}, function( err, result ) {
		if ( err ) {
			grunt.log.error( err );
			return done( false );
		}

		var authors,
			tracked = {};
		authors = result.split( "\n" ).reverse().filter(function( author ) {
			var first = !tracked[ author ];
			tracked[ author ] = true;
			return first;
		}).join( "\n" );
		grunt.log.writeln( authors );
		done();
	});
});

};