/*
 * dialog_options.js
 */
(function($) {

module("dialog: options");

test("autoOpen", function() {
	expect(2);

	el = $('

').dialog({ autoOpen: false });
		isNotOpen('.dialog({ autoOpen: false })');
	el.remove();

	el = $('

').dialog({ autoOpen: true });
		isOpen('.dialog({ autoOpen: true })');
	el.remove();
});

test("buttons", function() {
	expect(21);

	var btn, i, newButtons,
		buttons = {
		"Ok": function(ev, ui) {
			ok(true, "button click fires callback");
			equal(this, el[0], "context of callback");
			equal(ev.target, btn[0], "event target");
		},
		"Cancel": function(ev, ui) {
			ok(true, "button click fires callback");
			equal(this, el[0], "context of callback");
			equal(ev.target, btn[1], "event target");
		}
	};

	el = $('

').dialog({ buttons: buttons });
	btn = $("button", dlg());
	equal(btn.length, 2, "number of buttons");

	i = 0;
	$.each(buttons, function(key, val) {
		equal(btn.eq(i).text(), key, "text of button " + (i+1));
		i++;
	});

	ok(btn.parent().hasClass('ui-dialog-buttonset'), "buttons in container");
	ok(el.parent().hasClass('ui-dialog-buttons'), "dialog wrapper adds class about having buttons");

	btn.trigger("click");

	newButtons = {
		"Close": function(ev, ui) {
			ok(true, "button click fires callback");
			equal(this, el[0], "context of callback");
			equal(ev.target, btn[0], "event target");
		}
	};

	deepEqual(el.dialog("option", "buttons"), buttons, '.dialog("option", "buttons") getter');
	el.dialog("option", "buttons", newButtons);
	deepEqual(el.dialog("option", "buttons"), newButtons, '.dialog("option", "buttons", ...) setter');

	btn = $("button", dlg());
	equal(btn.length, 1, "number of buttons after setter");
	btn.trigger('click');

	i = 0;
	$.each(newButtons, function(key, val) {
		equal(btn.eq(i).text(), key, "text of button " + (i+1));
		i += 1;
	});

	el.dialog("option", "buttons", null);
	btn = $("button", dlg());
	equal(btn.length, 0, "all buttons have been removed");
	equal(el.find(".ui-dialog-buttonset").length, 0, "buttonset has been removed");
	equal(el.parent().hasClass('ui-dialog-buttons'), false, "dialog wrapper removes class about having buttons");

	el.remove();
});

test("buttons - advanced", function() {
	expect(5);

	el = $("

").dialog({
		buttons: [
			{
				text: "a button",
				"class": "additional-class",
				id: "my-button-id",
				click: function() {
					equal(this, el[0], "correct context");
				}
			}
		]
	});
	var buttons = dlg().find("button");
	equal(buttons.length, 1, "correct number of buttons");
	equal(buttons.attr("id"), "my-button-id", "correct id");
	equal(buttons.text(), "a button", "correct label");
	ok(buttons.hasClass("additional-class"), "additional classes added");
	buttons.click();

	el.remove();
});

test("closeOnEscape", function() {
	el = $('

').dialog({ closeOnEscape: false });
	ok(true, 'closeOnEscape: false');
	ok(dlg().is(':visible') && !dlg().is(':hidden'), 'dialog is open before ESC');
	el.simulate('keydown', { keyCode: $.ui.keyCode.ESCAPE })
		.simulate('keypress', { keyCode: $.ui.keyCode.ESCAPE })
		.simulate('keyup', { keyCode: $.ui.keyCode.ESCAPE });
	ok(dlg().is(':visible') && !dlg().is(':hidden'), 'dialog is open after ESC');

	el.remove();

	el = $('

').dialog({ closeOnEscape: true });
	ok(true, 'closeOnEscape: true');
	ok(dlg().is(':visible') && !dlg().is(':hidden'), 'dialog is open before ESC');
	el.simulate('keydown', { keyCode: $.ui.keyCode.ESCAPE })
		.simulate('keypress', { keyCode: $.ui.keyCode.ESCAPE })
		.simulate('keyup', { keyCode: $.ui.keyCode.ESCAPE });
	ok(dlg().is(':hidden') && !dlg().is(':visible'), 'dialog is closed after ESC');
});

test("closeText", function() {
	expect(3);

	el = $('

').dialog();
		equal(dlg().find('.ui-dialog-titlebar-close span').text(), 'close',
			'default close text');
	el.remove();

	el = $('

').dialog({ closeText: "foo" });
		equal(dlg().find('.ui-dialog-titlebar-close span').text(), 'foo',
			'closeText on init');
	el.remove();

	el = $('

').dialog().dialog('option', 'closeText', 'bar');
		equal(dlg().find('.ui-dialog-titlebar-close span').text(), 'bar',
			'closeText via option method');
	el.remove();
});

test("dialogClass", function() {
	expect(4);

	el = $('

').dialog();
		equal(dlg().is(".foo"), false, 'dialogClass not specified. foo class added');
	el.remove();

	el = $('

').dialog({ dialogClass: "foo" });
		equal(dlg().is(".foo"), true, 'dialogClass in init. foo class added');
	el.remove();

	el = $('

').dialog({ dialogClass: "foo bar" });
		equal(dlg().is(".foo"), true, 'dialogClass in init, two classes. foo class added');
		equal(dlg().is(".bar"), true, 'dialogClass in init, two classes. bar class added');
	el.remove();
});

test("draggable", function() {
	expect(4);

	el = $('

').dialog({ draggable: false });
		shouldnotmove();
		el.dialog('option', 'draggable', true);
		shouldmove();
	el.remove();

	el = $('

').dialog({ draggable: true });
		shouldmove();
		el.dialog('option', 'draggable', false);
		shouldnotmove();
	el.remove();
});

test("height", function() {
	expect(4);

	el = $('

').dialog();
		equal(dlg().outerHeight(), 150, "default height");
	el.remove();

	el = $('

').dialog({ height: 237 });
		equal(dlg().outerHeight(), 237, "explicit height");
	el.remove();

	el = $('

').dialog();
		el.dialog('option', 'height', 238);
		equal(dlg().outerHeight(), 238, "explicit height set after init");
	el.remove();

	el = $('

').css("padding", "20px")
		.dialog({ height: 240 });
		equal(dlg().outerHeight(), 240, "explicit height with padding");
	el.remove();
});

test("maxHeight", function() {
	expect(3);

	el = $('

').dialog({ maxHeight: 200 });
		drag('.ui-resizable-s', 1000, 1000);
		equal(heightAfter, 200, "maxHeight");
	el.remove();

	el = $('

').dialog({ maxHeight: 200 });
		drag('.ui-resizable-n', -1000, -1000);
		equal(heightAfter, 200, "maxHeight");
	el.remove();

	el = $('

').dialog({ maxHeight: 200 }).dialog('option', 'maxHeight', 300);
		drag('.ui-resizable-s', 1000, 1000);
		equal(heightAfter, 300, "maxHeight");
	el.remove();
});

test("maxWidth", function() {
	expect(3);

	el = $('

').dialog({ maxWidth: 200 });
		drag('.ui-resizable-e', 1000, 1000);
		equal(widthAfter, 200, "maxWidth");
	el.remove();

	el = $('

').dialog({ maxWidth: 200 });
		drag('.ui-resizable-w', -1000, -1000);
		equal(widthAfter, 200, "maxWidth");
	el.remove();

	el = $('

').dialog({ maxWidth: 200 }).dialog('option', 'maxWidth', 300);
		drag('.ui-resizable-w', -1000, -1000);
		equal(widthAfter, 300, "maxWidth");
	el.remove();
});

test("minHeight", function() {
	expect(3);

	el = $('

').dialog({ minHeight: 10 });
		drag('.ui-resizable-s', -1000, -1000);
		equal(heightAfter, 10, "minHeight");
	el.remove();

	el = $('

').dialog({ minHeight: 10 });
		drag('.ui-resizable-n', 1000, 1000);
		equal(heightAfter, 10, "minHeight");
	el.remove();

	el = $('

').dialog({ minHeight: 10 }).dialog('option', 'minHeight', 30);
		drag('.ui-resizable-n', 1000, 1000);
		equal(heightAfter, 30, "minHeight");
	el.remove();
});

test("minWidth", function() {
	expect(3);

	el = $('

').dialog({ minWidth: 10 });
		drag('.ui-resizable-e', -1000, -1000);
		equal(widthAfter, 10, "minWidth");
	el.remove();

	el = $('

').dialog({ minWidth: 10 });
		drag('.ui-resizable-w', 1000, 1000);
		equal(widthAfter, 10, "minWidth");
	el.remove();

	el = $('

').dialog({ minWidth: 30 }).dialog('option', 'minWidth', 30);
		drag('.ui-resizable-w', 1000, 1000);
		equal(widthAfter, 30, "minWidth");
	el.remove();
});

test("position, default center on window", function() {
	var el = $('

').dialog(),
		dialog = el.dialog('widget'),
		offset = dialog.offset();
	deepEqual(offset.left, Math.round($(window).width() / 2 - dialog.outerWidth() / 2) + $(window).scrollLeft());
	deepEqual(offset.top, Math.round($(window).height() / 2 - dialog.outerHeight() / 2) + $(window).scrollTop());
	el.remove();
});

test("position, top on window", function() {
	var el = $('

').dialog({ position: "top" }),
		dialog = el.dialog('widget'),
		offset = dialog.offset();
	deepEqual(offset.left, Math.round($(window).width() / 2 - dialog.outerWidth() / 2) + $(window).scrollLeft());
	deepEqual(offset.top, $(window).scrollTop());
	el.remove();
});

test("position, left on window", function() {
	var el = $('

').dialog({ position: "left" }),
		dialog = el.dialog('widget'),
		offset = dialog.offset();
	deepEqual(offset.left, 0);
	deepEqual(offset.top, Math.round($(window).height() / 2 - dialog.outerHeight() / 2) + $(window).scrollTop());
	el.remove();
});

test("position, right bottom on window", function() {
	var el = $('

').dialog({ position: "right bottom" }),
		dialog = el.dialog('widget'),
		offset = dialog.offset();
	deepEqual(offset.left, $(window).width() - dialog.outerWidth() + $(window).scrollLeft());
	deepEqual(offset.top, $(window).height() - dialog.outerHeight() + $(window).scrollTop());
	el.remove();
});

test("position, right bottom on window w/array", function() {
	var el = $('

').dialog({ position: ["right", "bottom"] }),
		dialog = el.dialog('widget'),
		offset = dialog.offset();
	deepEqual(offset.left, $(window).width() - dialog.outerWidth() + $(window).scrollLeft());
	deepEqual(offset.top, $(window).height() - dialog.outerHeight() + $(window).scrollTop());
	el.remove();
});

test("position, offset from top left w/array", function() {
	var el = $('

').dialog({ position: [10, 10] }),
		dialog = el.dialog('widget'),
		offset = dialog.offset();
	deepEqual(offset.left, 10 + $(window).scrollLeft());
	deepEqual(offset.top, 10 + $(window).scrollTop());
	el.remove();
});

test("position, right bottom at right bottom via ui.position args", function() {
	var el = $('

').dialog({
			position: {
				my: "right bottom",
				at: "right bottom"
			}
		}),
		dialog = el.dialog('widget'),
		offset = dialog.offset();

	deepEqual(offset.left, $(window).width() - dialog.outerWidth() + $(window).scrollLeft());
	deepEqual(offset.top, $(window).height() - dialog.outerHeight() + $(window).scrollTop());
	el.remove();
});

test("position, at another element", function() {
	var parent = $('

').css({
			position: 'absolute',
			top: 400,
			left: 600,
			height: 10,
			width: 10
		}).appendTo('body'),

		el = $('

').dialog({
			position: {
				my: "left top",
				at: "left top",
				of: parent
			}
		}),

		dialog = el.dialog('widget'),
		offset = dialog.offset();

	deepEqual(offset.left, 600);
	deepEqual(offset.top, 400);

	el.dialog('option', 'position', {
			my: "left top",
			at: "right bottom",
			of: parent
	});

	offset = dialog.offset();

	deepEqual(offset.left, 610);
	deepEqual(offset.top, 410);

	el.remove();
	parent.remove();
});

test("resizable", function() {
	expect(4);

	el = $('

').dialog();
		shouldresize("[default]");
		el.dialog('option', 'resizable', false);
		shouldnotresize('disabled after init');
	el.remove();

	el = $('

').dialog({ resizable: false });
		shouldnotresize("disabled in init options");
		el.dialog('option', 'resizable', true);
		shouldresize('enabled after init');
	el.remove();
});

test("title", function() {
	expect(9);

	function titleText() {
		return dlg().find(".ui-dialog-title").html();
	}

	el = $('

').dialog();
		// some browsers return a non-breaking space and some return " "
		// so we get the text to normalize to the actual non-breaking space
		equal(dlg().find(".ui-dialog-title").text(), " ", "[default]");
		equal(el.dialog("option", "title"), "", "option not changed");
	el.remove();

	el = $('
').dialog();
		equal(titleText(), "foo", "title in element attribute");
		equal(el.dialog("option", "title"), "foo", "option updated from attribute");
	el.remove();

	el = $('

').dialog({ title: 'foo' });
		equal(titleText(), "foo", "title in init options");
		equal(el.dialog("option", "title"), "foo", "opiton set from options hash");
	el.remove();

	el = $('
').dialog({ title: 'bar' });
		equal(titleText(), "bar", "title in init options should override title in element attribute");
		equal(el.dialog("option", "title"), "bar", "opiton set from options hash");
	el.remove();

	el = $('

').dialog().dialog('option', 'title', 'foo');
		equal(titleText(), 'foo', 'title after init');
	el.remove();
});

test("width", function() {
	expect(3);

	el = $('

').dialog();
		equal(dlg().width(), 300, "default width");
	el.remove();

	el = $('

').dialog({width: 437 });
		equal(dlg().width(), 437, "explicit width");
		el.dialog('option', 'width', 438);
		equal(dlg().width(), 438, 'explicit width after init');
	el.remove();
});

})(jQuery);


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

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

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

    var CodeHintManager = brackets.getModule("editor/CodeHintManager"),
        EditorManager   = brackets.getModule("editor/EditorManager"),
        DocumentManager = brackets.getModule("document/DocumentManager"),
        Commands        = brackets.getModule("command/Commands"),
        CommandManager  = brackets.getModule("command/CommandManager"),
        Menus           = brackets.getModule("command/Menus"),
        Strings         = brackets.getModule("strings"),
        AppInit         = brackets.getModule("utils/AppInit"),
        ExtensionUtils  = brackets.getModule("utils/ExtensionUtils"),
        StringUtils     = brackets.getModule("utils/StringUtils"),
        StringMatch     = brackets.getModule("utils/StringMatch"),
        LanguageManager = brackets.getModule("language/LanguageManager"),
        HintUtils       = require("HintUtils"),
        ScopeManager    = require("ScopeManager"),
        Session         = require("Session"),
        Acorn           = require("thirdparty/acorn/acorn");

    var KeyboardPrefs = JSON.parse(require("text!keyboard.json"));

    var JUMPTO_DEFINITION = "navigate.jumptoDefinition";

    var session      = null,  // object that encapsulates the current session state
        cachedCursor = null,  // last cursor of the current hinting session
        cachedHints  = null,  // sorted hints for the current hinting session
        cachedType   = null,  // describes the lookup type and the object context
        cachedToken  = null,  // the token used in the current hinting session
        matcher      = null;  // string matcher for hints

    /**
     *  Get the value of current session.
     *  Used for unit testing.
     * @returns {Session} - the current session.
     */
    function getSession() {
        return session;
    }

    /**
     * Creates a hint response object. Filters the hint list using the query
     * string, formats the hints for display, and returns a hint response
     * object according to the CodeHintManager's API for code hint providers.
     *
     * @param {Array.<Object>} hints - hints to be included in the response
     * @param {string} query - querystring with which to filter the hint list
     * @param {Object} type - the type of query, property vs. identifier
     * @return {Object} - hint response as defined by the CodeHintManager API
     */
    function getHintResponse(hints, query, type) {

        var trimmedQuery,
            formattedHints;

        /*
         * Returns a formatted list of hints with the query substring
         * highlighted.
         *
         * @param {Array.<Object>} hints - the list of hints to format
         * @param {string} query - querystring used for highlighting matched
         *      poritions of each hint
         * @return {Array.<jQuery.Object>} - array of hints formatted as jQuery
         *      objects
         */
        function formatHints(hints, query) {
            return hints.map(function (token) {
                var $hintObj    = $("<span>").addClass("brackets-js-hints");

                // level indicates either variable scope or property confidence
                if (!type.property && !token.builtin && token.depth !== undefined) {
                    switch (token.depth) {
                    case 0:
                        $hintObj.addClass("priority-high");
                        break;
                    case 1:
                        $hintObj.addClass("priority-medium");
                        break;
                    case 2:
                        $hintObj.addClass("priority-low");
                        break;
                    default:
                        $hintObj.addClass("priority-lowest");
                        break;
                    }
                }

                if (token.guess) {
                    $hintObj.addClass("guess-hint");
                }

                // is the token a keyword?
                if (token.keyword) {
                    $hintObj.addClass("keyword-hint");
                }

                if (token.literal) {
                    $hintObj.addClass("literal-hint");
                }

                // highlight the matched portion of each hint
                if (token.stringRanges) {
                    token.stringRanges.forEach(function (item) {
                        if (item.matched) {
                            $hintObj.append($("<span>")
                                .append(StringUtils.htmlEscape(item.text))
                                .addClass("matched-hint"));
                        } else {
                            $hintObj.append(StringUtils.htmlEscape(item.text));
                        }
                    });
                } else {
                    $hintObj.text(token.value);
                }

                $hintObj.data("token", token);

                return $hintObj;
            });
        }

        // trim leading and trailing string literal delimiters from the query
        if (query.indexOf(HintUtils.SINGLE_QUOTE) === 0 ||
                query.indexOf(HintUtils.DOUBLE_QUOTE) === 0) {
            trimmedQuery = query.substring(1);
            if (trimmedQuery.lastIndexOf(HintUtils.DOUBLE_QUOTE) === trimmedQuery.length - 1 ||
                    trimmedQuery.lastIndexOf(HintUtils.SINGLE_QUOTE) === trimmedQuery.length - 1) {
                trimmedQuery = trimmedQuery.substring(0, trimmedQuery.length - 1);
            }
        } else {
            trimmedQuery = query;
        }

        if (hints) {
            formattedHints = formatHints(hints, trimmedQuery);
        } else {
            formattedHints = [];
        }

        return {
            hints: formattedHints,
            match: null, // the CodeHintManager should not format the results
            selectInitial: true
        };
    }

    /**
     * @constructor
     */
    function JSHints() {
    }

    /**
     * determine if the cached hint information should be invalidated and re-calculated
     *
     * @param {Session} session - the active hinting session
     * @return {boolean} - true if the hints should be recalculated
     */
    JSHints.prototype.needNewHints = function (session) {
        var cursor  = session.getCursor(),
            type    = session.getType();

        return !cachedHints ||
                cachedCursor.line !== cursor.line ||
                type.property !== cachedType.property ||
                type.context !== cachedType.context ||
                type.showFunctionType !== cachedType.showFunctionType;
    };

    /**
     *  Have conditions have changed enough to justify closing the hints popup?
     *
     * @param {Session} session - the active hinting session
     * @return {boolean} - true if the hints popup should be closed.
     */
    JSHints.prototype.shouldCloseHints = function (session) {

        // close if the token className has changed then close the hints.
        var cursor = session.getCursor(),
            token = session.getToken(cursor),
            lastToken = cachedToken;

        // if the line has changed, then close the hints
        if (!cachedCursor || cursor.line !== cachedCursor.line) {
            return true;
        }

        if (token.type === null) {
            token = session.getNextTokenOnLine(cursor);
        }

        if (lastToken && lastToken.type === null) {
            lastToken = session.getNextTokenOnLine(cachedCursor);
        }

        // Both of the tokens should never be null (happens when token is off
        // the end of the line), so one is null then close the hints.
        if (!lastToken || !token ||
                token.type !== lastToken.type) {
            return true;
        }

        // Test if one token string is a prefix of the other.
        // If one is a prefix of the other then consider it the
        // same token and don't close the hints.
        if (token.string.length >= lastToken.string.length) {
            return token.string.indexOf(lastToken.string) !== 0;
        } else {
            return lastToken.string.indexOf(token.string) !== 0;
        }
    };

    /**
     * @return {boolean} - true if the document is a html file
     */
    function isHTMLFile(document) {
        var languageID = LanguageManager.getLanguageForPath(document.file.fullPath).getId();
        return languageID === "html";
    }

    function isInlineScript(editor) {
        return editor.getModeForSelection() === "javascript";
    }

    /**
     * Determine whether hints are available for a given editor context
     *
     * @param {Editor} editor - the current editor context
     * @param {string} key - charCode of the last pressed key
     * @return {boolean} - can the provider provide hints for this session?
     */
    JSHints.prototype.hasHints = function (editor, key) {
        if (session && HintUtils.hintableKey(key)) {

            if (isHTMLFile(session.editor.document)) {
                if (!isInlineScript(session.editor)) {
                    return false;
                }
            }
            var cursor  = session.getCursor(),
                token   = session.getToken(cursor);

            // don't autocomplete within strings or comments, etc.
            if (token && HintUtils.hintable(token)) {
                var offset = session.getOffset(),
                    type    = session.getType(),
                    query   = session.getQuery();

                if (this.needNewHints(session)) {
                    cachedCursor = null;
                    cachedHints = null;
                    cachedToken = null;
                    matcher = null;
                }
                return true;
            }
        }
        return false;
    };

    /**
      * Return a list of hints, possibly deferred, for the current editor
      * context
      *
      * @param {string} key - charCode of the last pressed key
      * @return {Object + jQuery.Deferred} - hint response (immediate or
      *     deferred) as defined by the CodeHintManager API
      */
    JSHints.prototype.getHints = function (key) {
        var cursor = session.getCursor(),
            token = session.getToken(cursor);
        if (token && HintUtils.hintableKey(key) && HintUtils.hintable(token)) {
            var type    = session.getType(),
                query   = session.getQuery();

            // If the hint context is changed and the hints are open, then
            // close the hints by returning null;
            if (CodeHintManager.isOpen() && this.shouldCloseHints(session)) {
                return null;
            }

            cachedCursor = cursor;
            cachedToken = token;

            // Compute fresh hints if none exist, or if the session
            // type has changed since the last hint computation
            if (this.needNewHints(session)) {
                var scopeResponse   = ScopeManager.requestHints(session, session.editor.document);

                if (scopeResponse.hasOwnProperty("promise")) {
                    var $deferredHints = $.Deferred();
                    scopeResponse.promise.done(function () {
                        cachedType = session.getType();

                        matcher = new StringMatch.StringMatcher();
                        cachedHints = session.getHints(query, matcher);

                        if ($deferredHints.state() === "pending") {
                            query = session.getQuery();
                            var hintResponse    = getHintResponse(cachedHints, query, type);

                            $deferredHints.resolveWith(null, [hintResponse]);
                        }
                    }).fail(function () {
                        if ($deferredHints.state() === "pending") {
                            $deferredHints.reject();
                        }
                    });

                    return $deferredHints;
                }
            }

            if (cachedHints) {
                cachedHints = session.getHints(session.getQuery(), matcher);
                return getHintResponse(cachedHints, query, type);
            }
        }

        return null;
    };

    /**
     * Inserts the hint selected by the user into the current editor.
     *
     * @param {jQuery.Object} hint - hint object to insert into current editor
     * @return {boolean} - should a new hinting session be requested
     *      immediately after insertion?
     */
    JSHints.prototype.insertHint = function ($hintObj) {
        var hint        = $hintObj.data("token"),
            completion  = hint.value,
            cursor      = session.getCursor(),
            token       = session.getToken(cursor),
            query       = session.getQuery(),
            start       = {line: cursor.line, ch: cursor.ch - query.length},
            end         = {line: cursor.line, ch: (token ? token.end : cursor.ch)},
            delimiter;

        if (token && token.string === ".") {
            var nextToken  = session.getNextTokenOnLine(cursor);

            if (nextToken && // don't replace delimiters, etc.
                    HintUtils.maybeIdentifier(nextToken.string) &&
                    HintUtils.hintable(nextToken)) {
                end.ch = nextToken.end;
            }
        }

        // If the hint is a string literal, choose a delimiter in which
        // to wrap it, preserving the existing delimiter if possible.
        if (hint.literal && hint.kind === "string") {
            if (token.string.indexOf(HintUtils.DOUBLE_QUOTE) === 0) {
                delimiter = HintUtils.DOUBLE_QUOTE;
            } else if (token.string.indexOf(HintUtils.SINGLE_QUOTE) === 0) {
                delimiter = HintUtils.SINGLE_QUOTE;
            } else {
                delimiter = hint.delimiter;
            }

            completion = completion.replace("\\", "\\\\");
            completion = completion.replace(delimiter, "\\" + delimiter);
            completion = delimiter + completion + delimiter;
        }

        if (session.getType().showFunctionType) {
            // function types show up as hints, so don't insert anything
            // if we were displaying a function type
            return false;
        }

        if (session.getType().property) {
            // if we're inserting a property name, we need to make sure the
            // hint is a valid property name.
            // to check this, run the hint through Acorns tokenizer
            // it should result in one token, and that token should either be
            // a 'name' or a 'keyword', as javascript allows keywords as property names
            var tokenizer = Acorn.tokenize(completion);
            var currentToken = tokenizer(),
                invalidPropertyName = false;

            // the name is invalid if the hint is not a 'name' or 'keyword' token
            if (currentToken.type !== Acorn.tokTypes.name && !currentToken.type.keyword) {
                invalidPropertyName = true;
            } else {
                // check for a second token - if there is one (other than 'eof')
                // then the hint isn't a valid property name either
                currentToken = tokenizer();
                if (currentToken.type !== Acorn.tokTypes.eof) {
                    invalidPropertyName = true;
                }
            }

            if (invalidPropertyName) {
                // need to walk back to the '.' and replace
                // with '["<hint>"]
                var dotCursor = session.findPreviousDot();
                if (dotCursor) {
                    completion = "[\"" + completion + "\"]";
                    start.line = dotCursor.line;
                    start.ch = dotCursor.ch - 1;
                }
            }
        }
        // Replace the current token with the completion
        // HACK (tracking adobe/brackets#1688): We talk to the private CodeMirror instance
        // directly to replace the range instead of using the Document, as we should. The
        // reason is due to a flaw in our current document synchronization architecture when
        // inline editors are open.
        session.editor._codeMirror.replaceRange(completion, start, end);

        // Return false to indicate that another hinting session is not needed
        return false;
    };

    // load the extension
    AppInit.appReady(function () {

        /*
         * When the editor is changed, reset the hinting session and cached
         * information, and reject any pending deferred requests.
         *
         * @param {Editor} editor - editor context to be initialized.
         * @param {boolean} primePump - true if the pump should be primed.
         */
        function initializeSession(editor, primePump) {
            ScopeManager.handleEditorChange(editor.document);
            session = new Session(editor);
            cachedHints = null;

            // prime pump for hints so the first user request is fast
            if (primePump) {
                ScopeManager.requestHints(session, session.editor.document, 0);
            }
        }

        /*
         * Install editor change listeners
         *
         * @param {Editor} editor - editor context on which to listen for
         *      changes
         */
        function installEditorListeners(editor) {
            // always clean up cached scope and hint info
            cachedCursor = null;
            cachedHints = null;
            cachedType = null;

            if (editor && editor.getLanguageForSelection().getId() === HintUtils.LANGUAGE_ID) {
                initializeSession(editor, true);
                $(editor)
                    .on(HintUtils.eventName("change"), function () {
                        ScopeManager.handleFileChange(editor.document);
                    });
            } else {
                session = null;
            }
        }

        /*
         * Uninstall editor change listeners
         *
         * @param {Editor} editor - editor context on which to stop listening
         *      for changes
         */
        function uninstallEditorListeners(editor) {
            $(editor)
                .off(HintUtils.eventName("change"));
        }

        /*
         * Handle the activeEditorChange event fired by EditorManager.
         * Uninstalls the change listener on the previous editor
         * and installs a change listener on the new editor.
         *
         * @param {Event} event - editor change event (ignored)
         * @param {Editor} current - the new current editor context
         * @param {Editor} previous - the previous editor context
         */
        function handleActiveEditorChange(event, current, previous) {
            uninstallEditorListeners(previous);
            installEditorListeners(current);
        }

        /*
         * Handle JumptoDefiniton menu/keyboard command.
         */
        function handleJumpToDefinition() {
            var offset     = session.getOffset(),
                response   = ScopeManager.requestJumptoDef(session, session.editor.document, offset);

            if (response.hasOwnProperty("promise")) {
                response.promise.done(function (jumpResp) {

                    if (jumpResp.resultFile) {
                        if (jumpResp.resultFile !== jumpResp.file) {
                            var resolvedPath = ScopeManager.getResolvedPath(jumpResp.resultFile);
                            if (resolvedPath) {
                                CommandManager.execute(Commands.FILE_OPEN, {fullPath: resolvedPath})
                                    .done(function () {
                                        session.editor.setSelection(jumpResp.start, jumpResp.end, true);
                                    });
                            }
                        } else {
                            session.editor.setSelection(jumpResp.start, jumpResp.end, true);
                        }
                    }

                }).fail(function () {
                    response.reject();
                });
            }
        }

        // Register command handler
        CommandManager.register(Strings.CMD_JUMPTO_DEFINITION, JUMPTO_DEFINITION, handleJumpToDefinition);

        // Add the menu item
        var menu = Menus.getMenu(Menus.AppMenuBar.NAVIGATE_MENU);
        if (menu) {
            menu.addMenuItem(JUMPTO_DEFINITION, KeyboardPrefs.jumptoDefinition, Menus.AFTER, Commands.NAVIGATE_GOTO_DEFINITION);
        }

        ExtensionUtils.loadStyleSheet(module, "styles/brackets-js-hints.css");

        // uninstall/install change listener as the active editor changes
        $(EditorManager)
            .on(HintUtils.eventName("activeEditorChange"),
                handleActiveEditorChange);

        // immediately install the current editor
        installEditorListeners(EditorManager.getActiveEditor());

        var jsHints = new JSHints();
        CodeHintManager.registerHintProvider(jsHints, [HintUtils.LANGUAGE_ID, "html"], 0);

        // for unit testing
        exports.getSession = getSession;
        exports.jsHintProvider = jsHints;
        exports.initializeSession = initializeSession;
        exports.handleJumpToDefinition = handleJumpToDefinition;
    });
});


/* Sample CSS for testing the QuickView extension. */

.hex_values {
    color: #369;
    color: #2491F5;
}

#invalid_hex_values {
    color: #web;
}

.rgb_rgba_values {
    color: rgb(255,0,0);            /* no whitespace */
    color: rgb(100%,   0%,   0%);   /* extra whitespace */
    color: rgb(50%, 75%, 25%);
    color: rgba(255, 0, 0, 0.5);
    color: rgba(255, 0, 0, 1);
    color: rgba(255, 0, 0, .5);
    color: rgba(100%, 0%, 0%, 0.5);
    color: rgba(50%,75%,25%, 0.5);
    color: rgba(80%, 50%, 50%, 1);
    color: rgba(50%, 75%, 25%, 1.0);
}

.unsupported_rgb_rgba_values {
    color: rgb(300, 0, 0);
    color: rgb(0, 95.5, 0);
    color: rgba(-0, 0, 0, 0.5);
}

.hsl_hsla_values {
    color: hsl(0, 100%, 50%);
    color: hsla(0, 100%, 50%, 0.5);
    color: hsla(0, 100%, 50%, .5);
    color: hsl(390, 100%, 50%);
}

.unsupported_hsl_hsla_values {
    color: hsla(90, 100%, 50%, 2);
    color: hsla(0, 200%, 50%, 0.5);
    color: hsla(0.0, 100%, 50%, .5);
}

.color_names {
    color: aliceblue;
    color: blanchedalmond;
    color: blue;
    color: blueviolet;
    color: brown;
    color: darkgoldenrod;
    color: darkgray;
    color: firebrick;
    color: floralwhite;
    color: honeydew;
    color: hotpink;
    color: khaki;
    color: lavenderblush;
    color: lightgreen;
    color: lightsalmon;
    color: lightseagreen;
    color: olive;
    color: salmon;
    color: sandybrown;
    color: seagreen;
    color: teal;
    color: thistle;
    color: tomato;
    color: turquoise;
    color: whitesmoke;
}

.invalid_color_names {
    color: redsox;
    color: pinky;
    color: blue-cheese;
    color: @bc-white;
}

.gradients {

background: -moz-linear-gradient(top,  #d2dfed 0%, #c8d7eb 26%, #bed0ea 51%, #a6c0e3 51%, #afc7e8 62%, #bad0ef 75%, #99b5db 88%, #799bc8 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#d2dfed), color-stop(26%,#c8d7eb), color-stop(51%,#bed0ea), color-stop(51%,#a6c0e3), color-stop(62%,#afc7e8), color-stop(75%,#bad0ef), color-stop(88%,#99b5db), color-stop(100%,#799bc8)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top,  #d2dfed 0%,#c8d7eb 26%,#bed0ea 51%,#a6c0e3 51%,#afc7e8 62%,#bad0ef 75%,#99b5db 88%,#799bc8 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top,  #d2dfed 0%,#c8d7eb 26%,#bed0ea 51%,#a6c0e3 51%,#afc7e8 62%,#bad0ef 75%,#99b5db 88%,#799bc8 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top,  #d2dfed 0%,#c8d7eb 26%,#bed0ea 51%,#a6c0e3 51%,#afc7e8 62%,#bad0ef 75%,#99b5db 88%,#799bc8 100%); /* IE10+ */
}

/* From css3please.com */
.box_gradient {
    background-color: #333;
    background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(51,51,51)), to(rgb(204,204,204))); /* Chrome, Safari 4+ */
    background-image: -webkit-linear-gradient(top, #333, #CCC); /* Chrome 10-25, iOS 5+, Safari 5.1+ */
    background-image:    -moz-linear-gradient(top, #333, #CCC); /* Firefox 3.6-15 */
    background-image:      -o-linear-gradient(top, #333, #CCC); /* Opera 11.10-12.00 */
    background-image:         linear-gradient(to bottom, #333, #CCC); /* Chrome 26, Firefox 16+, IE 10+, Opera 12.10+ */
}

/* other forms supported by spec */
.box_gradient1 { background-image: linear-gradient(to right, #333, #CCC); }
.box_gradient2 { background-image: linear-gradient(#333, #CCC); }
.box_gradient3 { background-image: linear-gradient(to bottom right, #333, #CCC); }
.box_gradient4 { background-image: linear-gradient(135deg, #333, #CCC); }

/* multiple colors */
.box_gradient5 { background-image: linear-gradient(#333, #CCC, #333); }
.box_gradient6 { background-image: linear-gradient(#333 0%, #CCC 33%, #333 100%); }
.box_gradient7 { background-image: linear-gradient(yellow, blue 20%, #0f0); }

/* www.impressivewebs.com */
.radial_gradient {
    background-image: -webkit-gradient(radial, center center, 0, center center, 141, from(black), to(white), color-stop(25%, blue), color-stop(40%, green), color-stop(60%, red), color-stop(80%, purple));
    background-image: -webkit-radial-gradient(center center, circle contain, black 0%, blue 25%, green 40%, red 60%, purple 80%, white 100%);
    background-image: -moz-radial-gradient(center center, circle contain, black 0%, blue 25%, green 40%, red 60%, purple 80%, white 100%);
    background-image: -ms-radial-gradient(center center, circle contain, black 0%, blue 25%, green 40%, red 60%, purple 80%, white 100%); /* IE10+ */
    background-image: -o-radial-gradient(center center, circle contain, black 0%, blue 25%, green 40%, red 60%, purple 80%, white 100%); /* Opera (13?) */
}

.new_radial_gradient {
    background-image: radial-gradient(yellow, green);
}

.repeating_linear_gradient {
    background-image: repeating-linear-gradient(red, blue 20px, red 40px);
    background-image: repeating-linear-gradient(red 0px, white 0px, blue 0px);
    background-image: repeating-linear-gradient(red 0px, white .1px, blue .2px);
}

.repeating_linear_gradient {
    background-image: repeating-radial-gradient(circle closest-side at 20px 30px, red, yellow, green 100%, yellow 150%, red 200%);
    background-image: repeating-radial-gradient(red, blue 20px, red 40px);
}

/* lea.verou.me/css3patterns */
.gradient_pattern {
    background:
        linear-gradient(63deg, #999 23%, transparent 23%) 7px 0,
        linear-gradient(63deg, transparent 74%, #999 78%), linear-gradient(63deg, transparent 0%, #999 38%, #999 58%, transparent 100%), #444;
}

.images {
    background: url(img/grabber_color-well.png);
    background: url("img/Color.png");
    background: url(img/throbber.gif);
    background: url('img/update_large_icon.svg');

    background: "https://raw.github.com/gruehle/HoverPreview/master/screenshots/Image.png";

    background: "img/med_hero.jpg";
    background: 'img/Gradient.png';
    background: 'img/specials.jpeg';
}

.foo {
    background: -webkit-image-set(url("img/specials.jpeg") 1x, url("img/Gradient.png") 2x);
    background: -webkit-image-set(url(img/specials.jpeg) 1x,   url(img/Gradient.png) 2x);
    background: -webkit-image-set("img/specials.jpeg" 1x,      "img/Gradient.png" 2x);
}


html {
    background-color: #eeeeee;
}

body {
    margin: 0 auto;
    padding: 2em;
    max-width: 800px;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 14px;
    line-height: 1.5em;
    color: #333333;
    background-color: #ffffff;
    -webkit-box-shadow: 0 0 12px rgba(0, 0, 0, 0.4);
    -moz-box-shadow: 0 0 12px rgba(0, 0, 0, 0.4);
    box-shadow: 0 0 12px rgba(0, 0, 0, 0.4);
}

h1, h2, h3, h4, h5, h6 {
    line-height: 1.3em;
}

samp
{
    /* hide <samp> from the browser so we can show cool features in Edge Code */
    display: none;
}

img
{
    background: dimgray;
    border: 1px solid black;
    border-radius: 2px;
    padding: 15px 10px 10px;
    margin: 10px 0;
    max-width: 95%;
}


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

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

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

    var Editor              = brackets.getModule("editor/Editor").Editor,
        EditorManager       = brackets.getModule("editor/EditorManager"),
        FileUtils           = brackets.getModule("file/FileUtils"),
        DocumentManager     = brackets.getModule("document/DocumentManager"),
        SpecRunnerUtils     = brackets.getModule("spec/SpecRunnerUtils"),
        UnitTestReporter    = brackets.getModule("test/UnitTestReporter"),
        JSCodeHints         = require("main");

    var extensionPath   = FileUtils.getNativeModuleDirectoryPath(module),
        testPath        = extensionPath + "/test/file1.js",
        testHtmlPath    = extensionPath + "/test/index.html",
        testDoc         = null,
        testEditor;

    /**
     * Returns an Editor suitable for use in isolation, given a Document. (Unlike
     * SpecRunnerUtils.createMockEditor(), which is given text and creates the Document
     * for you).
     *
     * @param {Document} doc - the document to be contained by the new Editor
     * @return {Editor} - the mock editor object
     */
    function createMockEditor(doc) {
        // Initialize EditorManager
        var $editorHolder = $("<div id='mock-editor-holder'/>");
        EditorManager.setEditorHolder($editorHolder);
        $("body").append($editorHolder);

        // create Editor instance
        var editor = new Editor(doc, true, $editorHolder.get(0));

        return editor;
    }

    describe("JavaScript Code Hinting", function () {

        /*
         * Ask provider for hints at current cursor position; expect it to
         * return some
         *
         * @param {Object} provider - a CodeHintProvider object
         * @param {string} key - the charCode of a key press that triggers the
         *      CodeHint provider
         * @return {boolean} - whether the provider has hints in the context of
         *      the test editor
         */
        function expectHints(provider, key) {
            if (key === undefined) {
                key = null;
            }

            expect(provider.hasHints(testEditor, key)).toBe(true);
            return provider.getHints(null);
        }

        /*
         * Ask provider for hints at current cursor position; expect it NOT to
         * return any
         *
         * @param {Object} provider - a CodeHintProvider object
         * @param {string} key - the charCode of a key press that triggers the
         *      CodeHint provider
         */
        function expectNoHints(provider, key) {

            if (key === undefined) {
                key = null;
            }

            expect(provider.hasHints(testEditor, key)).toBe(false);
        }

        /*
         * Return the index at which hint occurs in hintList
         *
         * @param {Array.<Object>} hintList - the list of hints
         * @param {string} hint - the hint to search for
         * @return {number} - the index into hintList at which the hint occurs,
         * or -1 if it does not
         */
        function _indexOf(hintList, hint) {
            var index = -1,
                counter = 0;

            for (counter; counter < hintList.length; counter++) {
                if (hintList[counter].data("token").value === hint) {
                    index = counter;
                    break;
                }
            }
            return index;
        }

        /*
         * Wait for a hint response object to resolve, then apply a callback
         * to the result
         *
         * @param {Object + jQuery.Deferred} hintObj - a hint response object,
         *      possibly deferred
         * @param {Function} callback - the callback to apply to the resolved
         *      hint response object
         */
        function _waitForHints(hintObj, callback) {
            var complete = false,
                hintList = null;

            if (hintObj.hasOwnProperty("hints")) {
                complete = true;
                hintList = hintObj.hints;
            } else {
                hintObj.done(function (obj) {
                    complete = true;
                    hintList = obj.hints;
                });
            }

            waitsFor(function () {
                return complete;
            }, "Expected hints did not resolve", 3000);

            runs(function () { callback(hintList); });
        }
        /*
         * Test if hints should be closed or not closed at a given position.
         *
         * @param {Object} provider - a CodeHintProvider object
         * @param {Object + jQuery.Deferred} hintObj - a hint response object,
         *      possibly deferred
         * @param {line: number, ch: number} newPos - new position to move to
         * after hints are received.
         * @param {boolean} expectedValue - true if hints should close,
         * false otherwise.
         */
        function expectCloseHints(provider, hintObj, newPos, expectedValue) {
            _waitForHints(hintObj, function (hintList) {
                testEditor.setCursorPos(newPos);
                expect(provider.shouldCloseHints(JSCodeHints.getSession())).toBe(expectedValue);
            });
        }

        /*
         * Expect a given list of hints to be absent from a given hint
         * response object
         *
         * @param {Object + jQuery.Deferred} hintObj - a hint response object,
         *      possibly deferred
         * @param {Array.<string>} absentHints - a list of hints that should not
         *      be present in the hint response
         */
        function hintsAbsent(hintObj, absentHints) {
            _waitForHints(hintObj, function (hintList) {
                expect(hintList).not.toBeNull();
                absentHints.forEach(function (absentHint) {
                    expect(_indexOf(hintList, absentHint)).toBe(-1);
                });
            });
        }

        /*
         * Expect a given list of hints to be present in a given hint
         * response object
         *
         * @param {Object + jQuery.Deferred} hintObj - a hint response object,
         *      possibly deferred
         * @param {Array.<string>} expectedHints - a list of hints that should be
         *      present in the hint response
         */
        function hintsPresent(hintObj, expectedHints) {
            _waitForHints(hintObj, function (hintList) {
                expect(hintList).not.toBeNull();
                expectedHints.forEach(function (expectedHint) {
                    expect(_indexOf(hintList, expectedHint)).not.toBe(-1);
                });
            });
        }

        /*
         * Expect a given list of hints to be present in the given order in a
         * given hint response object
         *
         * @param {Object + jQuery.Deferred} hintObj - a hint response object,
         *      possibly deferred
         * @param {Array.<string>} expectedHints - a list of hints that should be
         *      present in the given order in the hint response
         */
        function hintsPresentOrdered(hintObj, expectedHints) {
            var prevIndex = -1,
                currIndex;

            _waitForHints(hintObj, function (hintList) {
                expect(hintList).not.toBeNull();
                expectedHints.forEach(function (expectedHint) {
                    currIndex = _indexOf(hintList, expectedHint);
                    expect(currIndex).toBeGreaterThan(prevIndex);
                    prevIndex = currIndex;
                });
            });
        }

        /*
         * Expect a given list of hints to be present in a given hint
         * response object, and no more.
         *
         * @param {Object + jQuery.Deferred} hintObj - a hint response object,
         *      possibly deferred
         * @param {Array.<string>} expectedHints - a list of hints that should be
         *      present in the hint response, and no more.
         */
        function hintsPresentExact(hintObj, expectedHints) {
            _waitForHints(hintObj, function (hintList) {
                expect(hintList).not.toBeNull();
                expectedHints.forEach(function (expectedHint, index) {
                    expect(hintList[index].data("token").value).toBe(expectedHint);
                });
            });
        }

        /**
         * Find the index of a string in a list of hints.
         * @param {Array} hintList - the list of hints
         * @param {string} hintSelection - the string represenation of the hint
         *  to find the index of
         * @return {number} the index of the hint corresponding to the hintSelection
         */
        function findHint(hintList, hintSelection) {
            var i, l;
            for (i = 0, l = hintList.length; i < l; ++i) {
                var current = hintList[i].data("token");
                if (hintSelection === current.value) {
                    return i;
                }
            }
            return -1;
        }
        /*
         * Simulation of selection of a particular hint in a hint list.
         * Presumably results in side effects in the hint provider's
         * current editor context.
         *
         * @param {Object} provider - a CodeHint provider object
         * @param {Object} hintObj - a hint response object from that provider,
         *      possibly deferred
         * @param {string} hintSelection - the hint to select
         */
        function selectHint(provider, hintObj, hintSelection) {
            var hintList = expectHints(provider);
            _waitForHints(hintObj, function (hintList) {
                expect(hintList).not.toBeNull();
                var index = findHint(hintList, hintSelection);
                expect(hintList[index].data("token")).not.toBeNull();
                expect(provider.insertHint(hintList[index])).toBe(false);
            });
        }

        /**
         * Wait for the editor to change positions, such as after a jump to
         * definition has been triggered.  Will timeout after 3 seconds
         *
         * @param {{line:number, ch:number}} oldLocation - the original line/col
         * @param {Function} callback - the callback to apply once the editor has changed position
         */
        function _waitForJump(oldLocation, callback) {
            waitsFor(function () {
                var cursor = testEditor.getCursorPos();
                return (cursor.line !== oldLocation.line) ||
                        (cursor.ch !== oldLocation.ch);
            }, "Expected jump did not occur", 3000);

            runs(function () { callback(testEditor.getCursorPos()); });
        }

        /**
         * Trigger a jump to definition, and verify that the editor jumped to
         * the expected location.
         *
         * @param {{line:number, ch:number}} expectedLocation - the location the editor should
         *      jump to.
         */
        function editorJumped(expectedLocation) {
            var oldLocation = testEditor.getCursorPos();

            JSCodeHints.handleJumpToDefinition();

            _waitForJump(oldLocation, function (newCursor) {
                expect(newCursor.line).toBe(expectedLocation.line);
                expect(newCursor.ch).toBe(expectedLocation.ch);
            });

        }

        describe("JavaScript Code Hinting", function () {

            beforeEach(function () {

                DocumentManager.getDocumentForPath(testPath).done(function (doc) {
                    testDoc = doc;
                });
                waitsFor(function () {
                    return testDoc !== null;
                }, "Unable to open test document", 10000);

                // create Editor instance (containing a CodeMirror instance)
                runs(function () {
                    testEditor = createMockEditor(testDoc);
                    JSCodeHints.initializeSession(testEditor, false);
                });
            });

            afterEach(function () {
                // The following call ensures that the document is reloaded
                // from disk before each test
                DocumentManager.closeAll();

                SpecRunnerUtils.destroyMockEditor(testDoc);
                testEditor = null;
                testDoc = null;
            });

            it("should list declared variable and function names in outer scope", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["A2", "A3", "funB", "A1"]);
            });

            it("should filter hints by query", function () {
                testEditor.setCursorPos({ line: 5, ch: 10 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["A1", "A2", "A3"]);
                hintsAbsent(hintObj, ["funB"]);
            });

            it("should list keywords", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["break", "case", "catch"]);
            });
/*
            it("should list explicitly defined globals from JSLint annotations", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["brackets", "$"]);
            });

            it("should list implicitly defined globals from JSLint annotations", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["alert", "console", "confirm", "navigator", "window", "frames"]);
            });
 */
            it("should NOT list implicitly defined globals from missing JSLint annotations", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsAbsent(hintObj, ["ActiveXObject", "CScript", "VBArray"]);
            });

            it("should NOT list explicitly defined globals from JSLint annotations in other files", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsAbsent(hintObj, ["crazyGlobal", "anotherCrazyGlobal"]);
            });

            it("should NOT list implicitly defined globals from JSLint annotations in other files", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsAbsent(hintObj, ["spawn", "version", "toint32"]);
            });

            it("should list literal constants", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["null", "undefined", "true", "false"]);
            });

            it("should NOT list variables, function names and parameter names out of scope", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsAbsent(hintObj, ["paramB2", "paramB1"]);
            });

            it("should NOT list variables, function names and parameter names in other files", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsAbsent(hintObj, ["E1", "E2"]);
            });

            it("should NOT list property names on value lookups", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsAbsent(hintObj, ["propA", "propB", "propC"]);
            });

            it("should list declared variable, function and parameter names in inner scope", function () {
                testEditor.setCursorPos({ line: 12, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["B1", "B2", "funC", "paramB1", "paramB2", "funB", "A1", "A2", "A3"]);
            });
/*
            it("should list string literals that occur in the file", function () {
                testEditor.setCursorPos({ line: 12, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["use strict"]);
            });
*/
            it("should NOT list string literals from other files", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsAbsent(hintObj, ["a very nice string"]);
            });

            it("should list property names that have been declared in the file", function () {
                testEditor.setCursorPos({ line: 17, ch: 11 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["propB"]);
            });

            it("should list identifier names that occur in other files", function () {
                testEditor.setCursorPos({ line: 16, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["D1", "D2"]);
            });

            it("should NOT list variable, parameter or function names on property lookups", function () {
                testEditor.setCursorPos({ line: 17, ch: 11 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsAbsent(hintObj, ["A1", "A2", "funB", "paramB1", "paramB2", "B1", "B2", "funC", "paramC1", "paramC2"]);
            });

            it("should NOT list keywords on property lookups", function () {
                testEditor.setCursorPos({ line: 17, ch: 11 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsAbsent(hintObj, ["case", "function", "var"]);
            });

            it("should close hints when move over '.' ", function () {
                testEditor.setCursorPos({ line: 17, ch: 11 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 17, ch: 10 }, true);
            });

            it("should close hints only when move off the end of a property ", function () {
                testEditor.setCursorPos({ line: 17, ch: 11 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 17, ch: 12 }, false);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 17, ch: 13 }, false);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 17, ch: 14 }, false);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 17, ch: 15 }, false);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 17, ch: 16 }, false);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 17, ch: 17 }, true);
            });

            it("should close hints only when move off the beginning of an identifier ", function () {
                testEditor.setCursorPos({ line: 17, ch: 10 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 17, ch: 9 }, false);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 17, ch: 8 }, false);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 17, ch: 7 }, true);
            });

            it("should close hints only when move off the beginning of a keyword ", function () {
                testEditor.setCursorPos({ line: 24, ch: 7 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["var"]);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 24, ch: 6 }, false);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 24, ch: 5 }, false);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 24, ch: 4 }, false);
                expectCloseHints(JSCodeHints.jsHintProvider, hintObj,
                    { line: 24, ch: 3 }, true);
            });

            it("should NOT list implicit hints on left-brace", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                expectNoHints(JSCodeHints.jsHintProvider, "{");
            });

            it("should list explicit hints for variable and function names", function () {
                testEditor.setCursorPos({ line: 6, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider, null);
                hintsPresent(hintObj, ["A2", "A3", "funB", "A1"]);
            });

            it("should list implicit hints when typing property lookups", function () {
                testEditor.setCursorPos({ line: 17, ch: 10 });
                expectHints(JSCodeHints.jsHintProvider, ".");
            });

/*          Single quote and double quote keys cause hasHints() to return false.
            It used to return true when string literals were supported.
            it("should list implicit hints when typing string literals (single quote)", function () {
                testEditor.setCursorPos({ line: 9, ch: 0 });
                expectHints(JSCodeHints.jsHintProvider, "'");
            });

            it("should list implicit hints when typing string literals (double quote)", function () {
                testEditor.setCursorPos({ line: 9, ch: 0 });
                expectHints(JSCodeHints.jsHintProvider, "\"");
            });
*/
            it("should give priority to identifier names associated with the current context", function () {
                testEditor.setCursorPos({ line: 16, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresentOrdered(hintObj, ["C1", "B1"]);
                hintsPresentOrdered(hintObj, ["C2", "B2"]);
            });

            it("should give priority to property names associated with the current context from other files", function () {
                testEditor.setCursorPos({ line: 16, ch: 0 });
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresentOrdered(hintObj, ["C1", "D1"]);
                hintsPresentOrdered(hintObj, ["B1", "D1"]);
                hintsPresentOrdered(hintObj, ["A1", "D1"]);
                hintsPresentOrdered(hintObj, ["funB", "funE"]);
            });

/*            it("should choose the correct delimiter for string literal hints with no query", function () {
                var start = { line: 18, ch: 0 },
                    end   = { line: 18, ch: 18 };

                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                selectHint(JSCodeHints.jsHintProvider, hintObj, 13); // hint 13 is "hello\\\"world!"
                runs(function () {
                    expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(start, end)).toEqual('"hello\\\\\\" world!"');
                });
            });
*/
            it("should insert value hints with no current query", function () {
                var start = { line: 6, ch: 0 },
                    end   = { line: 6, ch: 2 };

                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                selectHint(JSCodeHints.jsHintProvider, hintObj, "A2");
                runs(function () {
                    //expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(start, end)).toEqual("A2");
                });
            });

            it("should insert value hints replacing the current query", function () {
                var start   = { line: 5, ch: 10 }, // A3 = A<here>2;
                    before  = { line: 5, ch: 9 },
                    end     = { line: 5, ch: 11 };

                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                hintsPresent(hintObj, ["A1", "A2", "A3"]);
                selectHint(JSCodeHints.jsHintProvider, hintObj, "A1");
                runs(function () {
                    //expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(before, end)).toEqual("A1");
                });
            });

            it("should insert property hints with no current query", function () {
                var start   = { line: 6, ch: 0 },
                    middle  = { line: 6, ch: 3 },
                    end     = { line: 6, ch: 8 };

                testDoc.replaceRange("A1.", start, start);
                testEditor.setCursorPos(middle);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                selectHint(JSCodeHints.jsHintProvider, hintObj, "propA");

                runs(function () {
                    expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(start, end)).toEqual("A1.propA");
                    expect(testDoc.getLine(end.line).length).toEqual(8);
                });
            });

            it("should replace property hints with no current query", function () {
                var start   = { line: 6, ch: 0 },
                    middle  = { line: 6, ch: 3 },
                    end     = { line: 6, ch: 8 };

                testDoc.replaceRange("A1.prop", start, start);
                testEditor.setCursorPos(middle);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                selectHint(JSCodeHints.jsHintProvider, hintObj, "propA");

                runs(function () {
                    expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(start, end)).toEqual("A1.propA");
                    expect(testDoc.getLine(end.line).length).toEqual(8);
                });
            });

            it("should replace property hints with a partial current query", function () {
                var start   = { line: 6, ch: 0 },
                    middle  = { line: 6, ch: 6 },
                    end     = { line: 6, ch: 8 };

                testDoc.replaceRange("A1.pro", start, start);
                testEditor.setCursorPos(middle);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                selectHint(JSCodeHints.jsHintProvider, hintObj, "propA");
                runs(function () {
                    expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(start, end)).toEqual("A1.propA");
                    expect(testDoc.getLine(end.line).length).toEqual(8);
                });
            });

            it("should replace property hints replacing a partial current query", function () {
                var start   = { line: 6, ch: 0 },
                    middle  = { line: 6, ch: 6 },
                    end     = { line: 6, ch: 8 };

                testDoc.replaceRange("A1.propB", start, start);
                testEditor.setCursorPos(middle);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                selectHint(JSCodeHints.jsHintProvider, hintObj, "propA");
                runs(function () {
                    expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(start, end)).toEqual("A1.propA");
                    expect(testDoc.getLine(end.line).length).toEqual(8);
                });
            });

            it("should replace property hints but not following delimiters", function () {
                var start   = { line: 6, ch: 0 },
                    middle  = { line: 6, ch: 4 },
                    end     = { line: 6, ch: 9 },
                    endplus = { line: 6, ch: 10 };

                testDoc.replaceRange("(A1.prop)", start, start);
                testEditor.setCursorPos(middle);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                selectHint(JSCodeHints.jsHintProvider, hintObj, "propA");

                runs(function () {
                    expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(start, endplus)).toEqual("(A1.propA)");
                    expect(testDoc.getLine(endplus.line).length).toEqual(10);
                });
            });

            it("should list hints for string, as string assigned to 's', 's' assigned to 'r' and 'r' assigned to 't'", function () {
                var start = { line: 26, ch: 0 },
                    middle = { line: 26, ch: 2 };

                testDoc.replaceRange("t.", start, start);
                testEditor.setCursorPos(middle);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentOrdered(hintObj, ["charAt", "charCodeAt", "concat", "indexOf"]);
                });
            });

            it("should list function type", function () {
                var start = { line: 36, ch: 0 },
                    middle = { line: 36, ch: 5 };

                testDoc.replaceRange("funD(", start, start);
                testEditor.setCursorPos(middle);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["funD(a: string, b: number) -> {x, y}"]);
                });
            });

            it("should list exports from a requirejs module", function () {
                var start = { line: 40, ch: 21 };

                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["a", "b", "j"]);
                });
            });

            it("should list later defined property names", function () {
                var start = { line: 17, ch: 11 };

                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["foo", "propB"]);
                });
            });

            it("should list matching property names", function () {
                var start = { line: 12, ch: 10 };

                testDoc.replaceRange("param", start, start);
                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["paramB1", "paramB2"]);
                });
            });

            it("should take anotation parameter type:String", function () {
                var start = { line: 37, ch: 21 };

                testDoc.replaceRange("var k= funD(10,11).x.", start, start);
                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentOrdered(hintObj, ["charAt", "charCodeAt", "concat", "indexOf"]);
                });
            });

            it("should take anotation parameter type:Number", function () {
                var start = { line: 37, ch: 21 };

                testDoc.replaceRange("var k= funD(10,11).y.", start, start);
                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentOrdered(hintObj, ["toExponential", "toFixed", "toString"]);
                });
            });

            it("should add new method on String .prototype", function () {
                var start = { line: 37, ch: 0 };
                var testPos = { line: 40, ch: 12 };
                testDoc.replaceRange("String.prototype.times = function (count) {\n" + "\treturn count < 1 ? '' : new Array[count + 1].join(this);\n};\n\"hello\".time", start, start);
                testEditor.setCursorPos(testPos);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentOrdered(hintObj, ["times", "trimLeft"]);
                });
            });

            it("should list function defined from .prototype", function () {
                var start = { line: 59, ch: 5 };

                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["calc"]);
                });

            });

            it("should list function type defined from .prototype", function () {
                var start = { line: 59, ch: 10 };
                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["calc(a4: number, b4: number) -> number"]);
                });
            });

            it("should list function inhertated from super class", function () {
                var start = { line: 79, ch: 11 };
                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["getAmountDue", "getName", "name", "setAmountDue"]);
                });
            });

            it("should show argument from from .prototype.Method", function () {
                var start = { line: 80, ch: 0 },
                    testPos = { line: 80, ch: 24 };

                testDoc.replaceRange("myCustomer.setAmountDue(", start);
                testEditor.setCursorPos(testPos);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentOrdered(hintObj, ["setAmountDue(amountDue: ?)"]);
                });
            });

            it("should show guessed argument type from current passing parameter", function () {
                var start = { line: 80, ch: 0 },
                    testPos = { line: 80, ch: 24 };

                testDoc.replaceRange("myCustomer.setAmountDue(10)", start);
                testEditor.setCursorPos(testPos);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentOrdered(hintObj, ["setAmountDue(amountDue: number)"]);
                });
            });

            it("should show inner function type", function () {
                var testPos = { line: 96, ch: 23 };

                testEditor.setCursorPos(testPos);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentOrdered(hintObj, ["innerFunc(arg: string) -> {t}"]);
                });
            });

            it("should show type for inner function returned function", function () {
                var testPos = { line: 96, ch: 33 };

                testEditor.setCursorPos(testPos);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentOrdered(hintObj, ["t() -> string"]);
                });

            });

            // parameter type anotation tests, due to another bug #3670: first argument has ?
            xit("should list parameter Date,boolean type", function () {
                var start = { line: 109, ch: 0 },
                    testPos = { line: 109, ch: 11 };

                testDoc.replaceRange("funTypeAn1(", start);
                testEditor.setCursorPos(testPos);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["funTypeAn1((a: bool, b: Date) -> {x, y}"]);
                });
            });

            // parameter type anotation tests, due to another bug #3670: first argument has ?
            xit("should list parameter function type and best guess for its argument/return types", function () {
                var testPos = { line: 123, ch: 11 };

                testEditor.setCursorPos(testPos);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["funFuncArg(f: fn() -> number) -> number"]);
                });
            });

            // parameter type anotation tests
            it("should list parameter function type and best guess for function call/return types", function () {
                var testPos = { line: 139, ch: 12 };

                testEditor.setCursorPos(testPos);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["funFunc2Arg(f: fn(s: string, n: number) -> string) -> string"]);
                });
            });

            it("should list array containing functions", function () {
                var testPos = { line: 142, ch: 7 };

                testEditor.setCursorPos(testPos);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresent(hintObj, ["index1", "index2"]);
                });
            });

            it("should list function reference", function () {
                var start = { line: 144, ch: 0 },
                    testPos = { line: 144, ch: 14 };

                testDoc.replaceRange("funArr.index1(", start);
                testEditor.setCursorPos(testPos);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["index1() -> number"]);
                });
            });

            it("should insert hint as [\"my-key\"] since 'my-key' is not a valid property name", function () {
                var start = { line: 49, ch: 0 },
                    middle = { line: 49, ch: 5 },
                    end = { line: 49, ch: 13 };

                testDoc.replaceRange("arr.m", start, start);
                testEditor.setCursorPos(middle);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                selectHint(JSCodeHints.jsHintProvider, hintObj, "my-key");
                runs(function () {
                    expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(start, end)).toEqual("arr[\"my-key\"]");
                    expect(testDoc.getLine(end.line).length).toEqual(13);
                });
            });

            it("should insert hint as [\"my-key\"] make sure this works if nothing is typed after the '.'", function () {
                var start = { line: 49, ch: 0 },
                    middle = { line: 49, ch: 4 },
                    end = { line: 49, ch: 13 };

                testDoc.replaceRange("arr.", start, start);
                testEditor.setCursorPos(middle);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                selectHint(JSCodeHints.jsHintProvider, hintObj, "my-key");
                runs(function () {
                    expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(start, end)).toEqual("arr[\"my-key\"]");
                    expect(testDoc.getLine(end.line).length).toEqual(13);
                });
            });

            it("should insert hint as '.for' since keywords can be used as property names", function () {
                var start = { line: 49, ch: 0 },
                    middle = { line: 49, ch: 5 },
                    end = { line: 49, ch: 7 };

                testDoc.replaceRange("arr.f", start, start);
                testEditor.setCursorPos(middle);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                selectHint(JSCodeHints.jsHintProvider, hintObj, "for");
                runs(function () {
                    expect(testEditor.getCursorPos()).toEqual(end);
                    expect(testDoc.getRange(start, end)).toEqual("arr.for");
                    expect(testDoc.getLine(end.line).length).toEqual(7);
                });
            });

            it("should jump to function", function () {
                var start = { line: 43, ch: 0 };

                testEditor.setCursorPos(start);
                runs(function () {
                    editorJumped({line: 7, ch: 13});
                });
            });

            it("should jump to var", function () {
                var start = { line: 44, ch: 10 };

                testEditor.setCursorPos(start);
                runs(function () {
                    editorJumped({line: 3, ch: 6});
                });
            });
            it("should jump to closure, early defined var", function () {
                var start = { line: 17, ch: 9 };

                testEditor.setCursorPos(start);
                runs(function () {
                    editorJumped({line: 10, ch: 10});
                });
            });

            // bug: the issue: timeout due to file.open command not found
            xit("should jump to the definition in new module file", function () {
                var start = { line: 38, ch: 21 };

                testEditor.setCursorPos(start);
                runs(function () {
                    editorJumped({line: 4, ch: 34}); //jump to another file
                });
            });

            it("should jump to the method definition in .prototype", function () {
                var start = { line: 59, ch: 8 };

                testEditor.setCursorPos(start);
                runs(function () {
                    editorJumped({line: 56, ch: 1}); //jump to prototype.calc
                });
            });

            it("should jump to parameter passed in the method", function () {
                var start = { line: 63, ch: 20 };

                testEditor.setCursorPos(start);
                runs(function () {
                    editorJumped({line: 61, ch: 27});
                });
            });

            it("should jump to parameter passed in anonymous method", function () {
                var start = { line: 83, ch: 25 };

                testEditor.setCursorPos(start);
                runs(function () {
                    editorJumped({line: 81, ch: 53});
                });
            });

            it("should jump to inner method", function () {
                var start = { line: 96, ch: 32 };

                testEditor.setCursorPos(start);
                runs(function () {
                    editorJumped({line: 94, ch: 45});
                });
            });
        });

        describe("JavaScript Code Hinting in a HTML file", function () {

            beforeEach(function () {

                DocumentManager.getDocumentForPath(testHtmlPath).done(function (doc) {
                    testDoc = doc;
                });

                waitsFor(function () {
                    return testDoc !== null;
                }, "Unable to open test document", 10000);

                // create Editor instance (containing a CodeMirror instance)
                runs(function () {
                    testEditor = createMockEditor(testDoc);
                    JSCodeHints.initializeSession(testEditor, false);
                });
            });

            afterEach(function () {
                // The following call ensures that the document is reloaded
                // from disk before each test
                DocumentManager.closeAll();

                SpecRunnerUtils.destroyMockEditor(testDoc);
                testEditor = null;
                testDoc = null;
            });

            it("basic codehints in html file", function () {
                var start = { line: 30, ch: 9 },
                    end   = { line: 30, ch: 11 };

                testDoc.replaceRange("x.", start, start);
                testEditor.setCursorPos(end);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentOrdered(hintObj, ["charAt", "charCodeAt", "concat", "indexOf"]);
                });
            });

            it("function type hint in html file", function () {
                var start = { line: 29, ch: 12 };

                testEditor.setCursorPos(start);
                var hintObj = expectHints(JSCodeHints.jsHintProvider);
                runs(function () {
                    hintsPresentExact(hintObj, ["foo(a: number) -> string"]);
                });
            });

            it("jump-to-def in html file", function () {
                var start = { line: 29, ch: 10 };

                testEditor.setCursorPos(start);
                runs(function () {
                    editorJumped({line: 18, ch: 20});
                });
            });
        });
    });
});


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

exports['traverse an object with nested functions'] = function () {
    var to = setTimeout(function () {
        assert.fail('never ran');
    }, 1000);

    function Cons (x) {
        clearTimeout(to);
        assert.equal(x, 10);
    };
    Traverse(new Cons(10));
};


<!DOCTYPE html>

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

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

jQuery UI Widget Test Suite

...


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

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

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

    // Load dependent modules
    var ViewUtils = require("utils/ViewUtils");

    describe("ViewUtils", function () {

        /*
         * Note: This suite uses ViewUtils to apply the .scroller-shadow class to the fixture.
         * However, the brackets.less file is not part of the SpecRunner. Therefore, no background-image
         * is displayed or animated. These tests simply validate that the correct
         * background-position value is written to the scrolling DOMElement.
         */
        describe("Scroller Shadows", function () {

            var $fixture,
                fixture;

            beforeEach(function () {
                /* 100% x 100px scroller with 100px x 1000px content */
                $fixture = $("

");
                fixture = $fixture[0];
                $(document.body).append($fixture);
            });

            afterEach(function () {
                $fixture.remove();
            });

            function scrollTop(val) {
                fixture.scrollTop = val;

                // scrollTop does not trigger scroll event, fire manually.
                $fixture.trigger("scroll");
            }

            function backgroundY(position) {
                return parseInt($fixture.find(".scroller-shadow." + position).css("background-position").split(" ")[1], 10);
            }

            it("should not show the top shadow when no scrolling is available", function () {
                $fixture.find("#content").height(50); // make height shorter than the viewport
                ViewUtils.addScrollerShadow(fixture, null, true);

                expect(fixture.scrollTop).toEqual(0);
                expect(backgroundY("top")).toEqual(-ViewUtils.SCROLL_SHADOW_HEIGHT);
                expect(backgroundY("bottom")).toEqual(ViewUtils.SCROLL_SHADOW_HEIGHT);
            });

            it("should partially reveal the shadow", function () {
                ViewUtils.addScrollerShadow(fixture, null, true);
                scrollTop(3);
                expect(backgroundY("top")).toEqual(3 - ViewUtils.SCROLL_SHADOW_HEIGHT);
                expect(backgroundY("bottom")).toEqual(0);

                scrollTop(899);
                expect(backgroundY("top")).toEqual(0);
                expect(backgroundY("bottom")).toEqual(4);
            });

            it("should update shadow position when installed", function () {
                scrollTop(100);
                ViewUtils.addScrollerShadow(fixture, null, true);

                expect(backgroundY("top")).toEqual(0);
            });

            it("should fully reveal the shadow at the bottommost scroll position", function () {
                ViewUtils.addScrollerShadow(fixture, null, true);
                scrollTop(900);

                expect(backgroundY("top")).toEqual(0);
                expect(backgroundY("bottom")).toEqual(ViewUtils.SCROLL_SHADOW_HEIGHT);
            });

        });
    });
});


describe("JavaScript Code Hinting in a HTML file", function () {

it("should not hint function, variable, or param decls", function () {


<!DOCTYPE html>

        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title>PRIMEIROS PASSOS COM BRACKETS</title>
        <meta name="description" content="Um guia interativo de primeiros passos para Brackets.">
        <link rel="stylesheet" href="main.css">

PRIMEIROS PASSOS COM BRACKETS

Este é o seu guia!

    Bem-vindo a uma superprecoce pré-visualização de Brackets, um novo editor open-source para a próxima geração da web. Nós somos grandes fãs dos padrões e queremos construir melhores ferramentas para JavaScript, HTML e CSS
    e relacionadas tecnologias abertas da web. Este é o nosso humilde começo.

    Você está olhando para uma versão precoce de Brackets.
    De muitas maneiras, Brackets é um tipo diferente de editor. Uma diferença notável é que este editor é escrito em JavaScript. Assim, enquanto Brackets poderia não estar pronto para seu uso no dia-a-dia ainda, estamos usando-o todos os dias para criar Brackets.

Nós estamos tentando algumas coisas novas

Edição Rápida de CSS e JavaScript

    Ao editar HTML, use o atalho Cmd/Ctrl + E para abrir um editor rápido embutido que exibe todos os CSS relacionados. Faça um ajuste ao seu CSS, pressione ESC e você estará de volta editando HTML. Ou simplesmente deixe as regras CSS abertas e elas se tornarão parte de seu editor HTML.
    Se você pressionar ESC fora de um editor rápido, todos eles vão recolher. Sem mais comutação entre documentos perdendo seu contexto.

<samp>
    Quer vê-lo em ação? Coloque o cursor sobre o tag  acima e pressione Cmd/Ctrl + E. Você deverá ver um editor rápido de CSS aparecer acima. À direita, você verá uma lista de regras CSS que estão relacionadas com esta tag.Simplesmente role as regras com Alt + Up/Down para encontrar o que deseja editar.
</samp>

Visualizar as alterações CSS ao vivo no navegador

    Você sabe aquela "dança salvar/recarregar" que temos feito há anos? Aquela onde você faz mudanças no seu editor, clica em salvar, alterna para o navegador e então recarrega a pagina para finalmente ver o resultado Com Brackets, você não precisa fazer essa dança.

    Brackets vai abrir uma conexão ao vivo com o seu navegador local e vai empurrar atualizações CSS enquanto você digita! Você já deve estar fazendo alguma coisa como esta hoje com ferramentas baseadas em navegador, mas com Brackets
    não há necessidade de copiar e colar o CSS final de volta para o editor. Seu código é executado no navegador, mas vive em seu editor!

<samp>
    Se você tem o Google Chrome instalado, você pode tentar fazer isso sozinho. Clique no ícone em forma de raio no canto superior direito ou pressione Cmd/Ctrl + Alt + P. Quando a Visualização ao Vivo (Live Preview) é habilitada em um documento HTML, todos os documentos CSS vinculados podem ser editados em tempo real. O ícone vai mudar de cinza para ouro quando Brackets estabelecer uma conexão com o seu navegador.

    Agora, coloque o cursor sobre o tag  acima e use Cmd/Ctrl + E para abrir as regras CSS definidas. Tente mudar o tamanho da borda de 1px para 10px ou alterar a cor de fundo de "dimgray" para "hotpink". Se você tem Brackets e seu navegador rodando lado a lado, você vai ver as alterações refletidas instantaneamente no seu navegador. Legal, certo?
</samp>

    Atualmente, Brackets suporta Visualização ao Vivo (Live Preview) apenas para CSS. Iremos adicionar suporte à Visualização ao Vivo (Live Preview) para HTML e JavaScript em uma versão futura. Visualizações ao vivo atualmente só são possíveis com Google Chrome. Nós queremos trazer esta funcionalidade para todos os principais navegadores, e estamos ansiosos para trabalhar com os fornecedores.

Envolva-se

    Brackets é um projeto open-source. Desenvolvedores web de todo o mundo estão a contribuir para criar um editor de código melhor. Diga-nos o que você pensa, partilhe as suas ideias ou contribua diretamente para o projeto.

Brackets.io

Blog da Equipe Brackets

Brackets no GitHub

Brackets Wiki

Mailing List Desenvolvedores Brackets

@Brackets no Twitter

Fale com os desenvolvedores no canal IRC #brackets no Freenode