AJS.log("autocomplete editor_plugin_src starting");
/**
 * Autocomplete dropdown appears when you press a trigger character the editor.
 */
(function() {

    tinymce.create('tinymce.plugins.AutoComplete', {
        init : function(ed) {

            ed.addCommand('mceConfAutocompleteLink', function() {
                tinymce.confluence.Autocompleter.Manager.shortcutFired("[");
            });
            ed.addCommand('mceConfAutocompleteImage', function() {
                tinymce.confluence.Autocompleter.Manager.shortcutFired("!");
            });

            ed.addShortcut("ctrl+shift+k", ed.getLang("AutoComplete"), "mceConfAutocompleteLink");
            ed.addShortcut("ctrl+shift+m", ed.getLang("AutoComplete"), "mceConfAutocompleteImage");

            var addAutocompleteHandlers = function (settings) {
                if (settings["confluence.prefs.editor.disable.autocomplete"]) {
                    return;
                }

                AJS.log("Autocomplete enabled, adding keyPress listener");

                // Certain keys prompt the autocomplete, e.g. typing [ goes into "link auto-complete" mode
                ed.onKeyPress.addToTop(tinymce.confluence.Autocompleter.Manager.triggerListener);
            };

            ed.onPostRender.add(function() {
                // The DOM might not necessarily be ready on editor post render (see similar code in the contextmenu plugin)
                AJS.$(function() {
                    if (AJS.params.remoteUser) {
                        AJS.$.getJSON(tinyMCE.settings.plugin_action_base_path + "/get-wysiwyg-settings.action", {}, addAutocompleteHandlers);
                    } else {
                        // Always enabled for anonymous users
                        addAutocompleteHandlers({});
                    }
                });
            });
        },

        getInfo : function() {
            return {
                longname : 'Auto Complete',
                author : 'Atlassian',
                authorurl : 'http://www.atlassian.com',
                version : tinymce.majorVersion + "." + tinymce.minorVersion
            };
        }
    });

    // Register plugin
    tinymce.PluginManager.add('autocomplete', tinymce.plugins.AutoComplete);
})();

tinymce.confluence.Autocompleter = {};

/**
 * Settings that each Autocomplete will be initialized on, depending on the trigger character used to activate the
 * autocomplete.
 */
AJS.toInit(function ($) {
    AJS.log("tinyMce-autocomplete-settings initialising");

tinymce.confluence.Autocompleter.Settings = {};
});

/**
 * Custom logging function allows for more structured output. log4javascript on the horizon.
 * @param owner the "class" this logger is for
 *
 * Params accepted by the returned log function:
 *  - caller : name of the calling method
 *  - desc : the actual log body
 *  - obj : an object or string to be rendered
 */
tinymce.confluence.Autocompleter.log = function (owner) {
    return function (caller, desc, obj) {
        // Log string objects on the same line, else on the next line
        var objIsStr = (obj != null && typeof obj != "object");
        var objStr = obj != null ? (objIsStr ? (" = " + obj) : " >") : "";
        AJS.log(owner + " - " + caller + " : " + (desc || null) + objStr);
        obj && !objIsStr && AJS.log(obj);
    };
};
AJS.log("tinyMce-autocomplete-util starting");
tinymce.confluence.Autocompleter.Util = (function() {

    var loadData = function (json, query, callback, field) {
        var hasErrors = json.statusMessage;
        var matrix;
        if (hasErrors) {
           matrix = [[{html: json.statusMessage, className: "error"}]];
        } else {
            var restMatrix = query ? AJS.REST.makeRestMatrixFromSearchData(json) : AJS.REST.makeRestMatrixFromData(json, field);
            matrix = AJS.REST.convertFromRest(restMatrix);
        }
        // do conversion
        callback(matrix, query);
    };

    return {
        /**
         * Returns the HTML of a AJS.dropdown link with an icon span. The icon span is required in the dropdown if we
         * want to use a sprite background for the link icon.
         * @param text escaped text of the dropdown item
         * @param className class name to be added to the link
         * @param iconClass class name to be added on the icon span
         * @return HTML string for the dropdown link
         */
        // we should remove this once AUI dropdown supports sprite icons
        dropdownLink : function(text, className, iconClass) {
            return "<a href='#' class='" + (className || "" ) + "'><span class='icon " + (iconClass || "") + "'></span><span>" + text + "</span></a>";
        },

        getRestData : function (autoCompleteControl, getUrl, getParams, val, callback, suggestionField) {
            var url = getUrl(val);
            if (url) {
                AJS.$.ajax({
                    type: "GET",
                    url: url,
                    data: getParams(autoCompleteControl, val),
                    success: function (json) {
                        loadData.call(autoCompleteControl, json, val, callback, suggestionField);
                    },
                    dataType: "json",
                    global: false,
                    timeout: 5000,
                    error: function (xml, status) { // ajax error handler
                        if (status == "timeout") {
                            loadData.call(autoCompleteControl, {statusMessage: "Timeout", query: val}, val, callback, suggestionField);
                        }
                    }
                });
            } else {
                // If no url, default items may be displayed - run the callback with no data.
                callback([], val);
            }
        }

    };

})(AJS.$);
AJS.log("tinyMce-autocomplete-control starting");
/**
 * Selects the word at the cursor and returns the word and the left/top location of the
 * bottom-left corner of the first word.
 *
 * @param options An options map including:
 *     - leadingChar: trigger character used to launch autocomplete
 *     - dontSuggest: Don't search based on text typed in the autocomplete span
 *     - backWords: the number of words to search backwards for
 */
tinymce.confluence.Autocompleter.Control = function(ed, options) {

    var log = tinymce.confluence.Autocompleter.log("Autocompleter.Control");

    /**
     * The Control to be returned.
     */
    var control = {},

        /**
         * This element wraps the search text and the trigger (if present).
         */
        AUTOCOMPLETE_ID = "autocomplete",

        /**
         * This element wraps the trigger character (e.g. @, [, !)
         */
        AUTOCOMPLETE_TRIGGER_ID = "autocomplete-trigger",

        /**
         * This element contains the text the user is searching for - it should always hold the cursor.
         */
        AUTOCOMPLETE_SEARCH_TEXT_ID = "autocomplete-search-text",

        adaptor = AJS.Editor.Adapter,
        rng = adaptor.getRange(),
        cursorPos = rng.startOffset,
        node = rng.startContainer,
        nodeText = node.nodeValue,
        leadingChar = options.leadingChar,
        selection = ed.selection,
        doc = ed.getDoc(),
        backWords = options.backWords || 0;

    if (AJS.$("#" + AUTOCOMPLETE_ID, doc).length) {
        log("init", "Autocomplete already exists, returning null.");
        return null;
    }
    control.backWords = backWords;
    control.maxResults = options.maxResults || 10;

    // Cursor may be in a <p> just outside of a TextNode, check for child node at startOffset
    if (nodeText == null && rng.collapsed && cursorPos && node.childNodes[cursorPos - 1]) {
        node = node.childNodes[cursorPos - 1];  // to the LEFT of the cursor
        nodeText = node.nodeValue;
        cursorPos = (nodeText && nodeText.length) || 0;
    }
    var text = "";
    // Cursor may still not be in a Text node, in which leave the text empty.
    if (nodeText != null) {
        text = (nodeText + "").substring(0, cursorPos);
        var pnode = node.previousSibling;
        while (pnode && pnode.nodeType == 3) {
            // add the text from any previous TextNodes
            text = pnode.nodeValue + text;
            pnode = pnode.previousSibling;
        }
    }
    // Disable trigger chars at the end of the certain strings e.g. “Hi there!”.
    // The regex allows "'<( left" and left' before the [ trigger and space, zero-width space, &nbsp; and em-dash
    // before all triggers.
    if (!backWords && text && !(/(["'<\(\u201c\u2018]\[|[\ufeff\u2014\s\xa0].)$/).test(text + leadingChar)) {
        log("init", "Cursor is in wrong word location to start autocomplete, returning null.");
        return null;
    }
    var $node = AJS.$(node);
    if ($node.closest("div.code, a[href], img, div.wysiwyg-macro-starttag, div.wysiwyg-macro-endtag").length || AJS.$("#property-panel:visible").length) {
        log("init", "Cursor is in wrong node to start autocomplete, returning null.");
        return null;
    }
    if (!leadingChar && nodeText == null) {
        log("init", "No text available for suggestion, range is", rng);

        // TODO - handle this (and weird TextNodes)
        nodeText = "";
    }

    // TODO - not this. See http://stackoverflow.com/questions/273789/is-there-a-version-of-javascripts-string-indexof-that-allows-for-regular-expre
    function regexLastIndexOf(str, regex, startpos) {
        !regex.global && (regex = new RegExp(regex.source, "g" + "i".slice(0, regex.ignoreCase) + "m".slice(0, regex.multiLine)));
        if (startpos == null) {
            startpos = str.length;
        } else if (startpos < 0) {
            startpos = 0;
        }
        var stringToWorkWith = str.substring(0, startpos + 1),
            lastIndexOf = -1,
            nextStop = 0;
        while ((result = regex.exec(stringToWorkWith)) != null) {
            lastIndexOf = result.index;
            regex.lastIndex = ++nextStop;
        }
        return lastIndexOf;
    }

    /**
     * Returns a jQuery-wrapped reference to the autocomplete container.
     */
    control.getContainer = function () {
        return AJS.$("#" + AUTOCOMPLETE_ID, doc);
    };

    /**
     * Starting at the given endpoint, search backward through text nodes until the requested number of words are
     * found.
     * @param node
     * @param offset
     * @param backWords
     */
    function findRangeStart(node, offset, backWords) {
        var nodeText, pNode;

        for (var i = 0; i < backWords; i++) {
            nodeText = node.nodeValue.substring(0, offset);
            offset = regexLastIndexOf(nodeText, (/\s+/), offset);
            while (offset == -1) {
                pNode = node.previousSibling;
                if (pNode && pNode.nodeType == 3) {
                    node = pNode;
                    nodeText = pNode.nodeValue;
                    if (nodeText) {
                        offset = regexLastIndexOf(nodeText, (/\s+/), nodeText.length);
                    }
                } else {
                    i = backWords;  // no point looking further
                    break;
                }
            }
        }

        return {
            node: node,
            offset: offset + 1
        };
    }

    var suggestionHtml = "";
    if (rng.collapsed && backWords && nodeText) {
        var rangeStart = findRangeStart(node, cursorPos, backWords);

        // Select from the cursor back to the start of the first word
        if (tinymce.isIE && backWords == 1) {
            var range = selection.getRng();
            range.moveStart("character", rangeStart.offset - cursorPos);
            range.select();
        } else {
            range = adaptor.createRange();
            range.setStart(rangeStart.node, rangeStart.offset);
            range.setEnd(node, cursorPos);
            selection.setRng(range);
        }
    }
    // Use the existing selection as the search term
    // TODO - html format is failing due to our preProcess on serializer. Fix that.
    suggestionHtml = selection.getContent({format : 'text'});
    log("init", "suggestionHtml", suggestionHtml);

    var el = AJS("span").attr("id", AUTOCOMPLETE_ID);
    if (leadingChar) {
        el.append(AJS("span").attr("id", AUTOCOMPLETE_TRIGGER_ID).text(leadingChar));
    }
    
    var $searchTextSpan = AJS("span").attr("id", AUTOCOMPLETE_SEARCH_TEXT_ID);
    $searchTextSpan.text(adaptor.HIDDEN_CHAR);
    el.append($searchTextSpan);
    selection.setNode(el[0]);

    var autocompleteSpan = control.getContainer();
    control.previousSearchText = "";
    control.settings = tinymce.confluence.Autocompleter.Settings[leadingChar || "["];  // default to link

    // Put the cursor inside the new span, at the end.
    var searchNode = AJS.$("#" + AUTOCOMPLETE_SEARCH_TEXT_ID, control.getContainer()),
        searchTextNode = searchNode[0].firstChild,
        cursorPosition = searchTextNode.nodeValue.length,
        selNode = AJS.$(doc.createElement("span")).text(suggestionHtml || adaptor.HIDDEN_CHAR);
    searchNode.empty().append(selNode);
    selection.select(selNode[0]);
    selection.collapse();

    var position = tinymce.DOM.getPos(autocompleteSpan[0]),
        height = autocompleteSpan.height();
    log("init", "position", position);
    log("init", "pixel offset", autocompleteSpan.offset());


    // Events
    var before = function (e) {
            if (control.onBeforeKey && !control.onBeforeKey(e, control.text())) {
                e.stopPropagation();
                e.preventDefault();
                log("after", "blocked by onBeforeKey");
                return false;
            }
        },
        after = function (e) {
            var rng = adaptor.getRange(),
                span = control.getContainer(),
                node = rng.startContainer,
                parent = node.parentNode;
            node.nodeType == 3 && (parent = parent.parentNode);
            var grandpa = parent.parentNode,
                outsideSearchSpan = parent != span[0] && grandpa != span[0];
            if (e.keyCode == 27 || outsideSearchSpan) {
                log("after", "dying because of: " + outsideSearchSpan ? "outside search span" : "escape pressed");
                control.die();
            } else if (control.onAfterKey && !control.onAfterKey(e, control.text())) {
                e.stopPropagation();
                e.preventDefault();
                log("after", "blocked by onAfterKey");
                return false;
            }
        },
        press = function (e) {
            if (control.onKeyPress && !control.onKeyPress(e, control.text())) {
                e.stopPropagation();
                e.preventDefault();
                log("after", "blocked by onKeyPress");
                return false;
            }
        },
        click = function (e) {
            if (control.getContainer()[0] != e.target.parentNode) {
                log("click", "Clicked outside of autocomplete, closing.");
                control.die();
            }
        };
    AJS.$(doc).keydown(before).keyup(after).keypress(press).click(click);


    // For Recent History and certain other searches, ignore the selected text for searching.
    control.word = "";
    if (!options.keepAlias) {
        control.word = suggestionHtml;
    } else {
        log("init", "No suggestion based on previous or selected text");
    }

    control.left = position.x;
    control.top = position.y + height;

    control.text = function (text) {
        var span = AJS.$("#" + AUTOCOMPLETE_SEARCH_TEXT_ID, control.getContainer());
        if (text != null) {
            span.html(text);
            return this;
        } else {
            text = AJS.escapeEntities(span.text());
            return text.replace(adaptor.HIDDEN_CHAR, "");
        }
    };

    /**
     * Replaces the autocomplete component with the given text, which may be empty.
     * If the given text IS empty, it will always be collapsed.
     * If the collapse parameter is true, the range will be collapsed at the end of the text.
     * @param text  string to replace autocomplete with
     * @param collapse if true, collapse range to end of text, else select text
     */
    var replaceWithTextAndGetRange = function(text, collapse) {
        adaptor.replaceWithTextAndGetRange(control.getContainer(), text, collapse);
        control.die();
        return rng;
    };

    control.replaceWithSelectedSearchText = function () {
        // Get the autocomplete search text and select the entire autocomplete
        var replaceText = control.text();
        log("replaceWithSelectedSearchText", replaceText);
        replaceWithTextAndGetRange(replaceText, false);
        return replaceText;
    };

    control.die = function (notrigger) {
        if (control.dying) {
            log("die", "Already dying, returning.");
            return;
        }
        control.dying = true;

        var container = control.getContainer();
        if (container.length) {
             log("die", "Tearing down autocomplete, cleaning up autocompleter");
            // Replace autocomplete span with its current text
            var replaceText = ((notrigger || options.backWords) ? "" : control.settings.ch) + control.text();
            rng = replaceWithTextAndGetRange(replaceText, true);
        }
        AJS.$(doc).unbind("keydown", before).unbind("keyup", after).unbind("click", click).unbind("keypress", press);
        AJS.Editor.Adapter.unbindScroll("autocomplete");
        AJS.$(document).unbind("click.autocomplete-outside");
        this.onDeath && this.onDeath();
    };

    AJS.Editor.Adapter.bindScroll("autocomplete", function () {
        control.die();
    });
    AJS.$(document).bind("click.autocomplete-outside", function (e) {
        if (!AJS.$(e.target).closest("#autocomplete-dropdown").length) {
            control.die();
        }
    });

    control.update = function (data) {
        AJS.Editor.Adapter.storeCurrentSelectionState();
        replaceWithTextAndGetRange("", true);
        this.settings.update(this,data);
        control.die();
    };

    control.removeSpan = function () {
        control.getContainer().remove();
    };
    return control;
};

AJS.log("tinyMce-autocomplete-manager starting");
tinymce.confluence.Autocompleter.Manager = (function ($) {

    var log = tinymce.confluence.Autocompleter.log("Autocompleter.Manager");

    /**
     * There will only be one autoCompleteControl active at a time so a reference to it can be shared across methods.
     */
    var autoCompleteControl;

    /**
    *  The input driven dropdown component that does most of the work.
    */
    var idd;

    /**
     * Called when the user hits a key combination at the end of some text to autocomplete.
     * If there is no text at the cursor, the user's Recent History is displayed instead.
     *
     * options include:
     *  - leadingChar - determines the type of autocomplete, e.g. [ , !
     *  - backWords - the number of words to search backwards for
     */
    var startAutoComplete = function (options) {
            log("startAutoComplete", "Started");
            autoCompleteControl = tinymce.confluence.Autocompleter.Control(AJS.Editor.Adapter.getEditor(), options);
            if (!autoCompleteControl) {
                return false;
            }
            var selectionHandler = function (e, selection) {
                e.preventDefault();
                var result = AJS.$.data(selection[0], "properties");
                if (result && typeof result.callback == "function") {
                    result.callback(autoCompleteControl);
                } else if (result.className != "menu-header") {
                    log("selectionHandler", "Inserting link from dropdown selection");
                    autoCompleteControl.update(result);
                }
            };
            var moveHandler = function (selection, dir) {
                var current = AJS.dropDown.current;
                if (selection && selection.find("a").is(".menu-header")) {
                    dir == "up" ? current.moveUp(): current.moveDown();
                }
            };

            var winWidth = AJS.$(window).width();
            idd = AJS.inputDrivenDropdown({
                onShow : function (dd) {
                    log("onShow", "Post-processing the dropdown");
                    dd.find("ol:has(a.menu-header)").addClass("top-menu-item");
                    $("#autocomplete-dropdown ol:empty").hide();

                    var iframe = AJS.Editor.Adapter.getEditorFrame();
                    iframe.shim && iframe.shim.hide();

                    dd.find("a.menu-header").unbind().click(function (e) {
                        e.preventDefault();
                        autoCompleteControl.die();
                    });
                    this.reset();
                },
                dropdownPlacement : function (dd) {
                    var parent = $("#autocomplete-dropdown"),
                        anchor = autoCompleteControl.getContainer();
                    if (!parent.length) {
                        parent = AJS("div").addClass("aui-dd-parent quick-nav-drop-down").attr("id", "autocomplete-dropdown").appendTo("body");
                    }
                    var offset = AJS.Editor.Adapter.offset(anchor),
                        overlap = parent.width() + offset.left - winWidth + 10,
                        gapForArrowY = 10,
                        gapForArrowX = 0,
                        top = offset.top + anchor.height() + gapForArrowY,
                        left = offset.left - (overlap > 0 ? overlap : 0) - gapForArrowX;
                    parent.append(dd).css({
                        position: "absolute",
                        top: top,
                        left: left
                    });
                    if (window.Raphael) {
                        if (idd.raphaelArrow) {
                            idd.raphaelArrow.canvas.style.left = offset.left + 4 + "px";
                            idd.raphaelArrow.canvas.style.top = top - 5 + "px";
                        } else {
                            var r = Raphael(offset.left + 4, top - 5, 12, 6);
                            r.path("M0.001,6.001l6.001-6.001,6.001,6.001").attr({
                                fill: "#f0f0f0",
                                stroke: "#bbb"
                            });
                            r.canvas.style.zIndex = 3000;
                            idd.raphaelArrow = r;
                        }
                    }
                },
                onDeath : function () {
                    $("#autocomplete-dropdown").remove();
                    idd.raphaelArrow && idd.raphaelArrow.remove && idd.raphaelArrow.remove();
                },
                ajsDropDownOptions: {
                    selectionHandler: selectionHandler,
                    moveHandler: moveHandler,
                    className : "autocomplete " + autoCompleteControl.settings.dropDownClassName
                },
                getDataAndRunCallback: function(val) {
                    autoCompleteControl.settings.getDataAndRunCallback &&
                    autoCompleteControl.settings.getDataAndRunCallback(autoCompleteControl, val,
                        function(matrix, query) {
                            matrix.unshift([
                                {
                                    className: "menu-header dropdown-prevent-highlight",
                                    href: "#",
                                    name: autoCompleteControl.settings.getHeaderText(autoCompleteControl, val)
                                }
                            ]);
                            matrix.push(autoCompleteControl.settings.getAdditionalLinks(autoCompleteControl, val));

                            // If the idd control is still active update it with the new data.
                            idd && idd.show(matrix, query, [query]);
                        }
                    );
                }
            });

            autoCompleteControl.onBeforeKey = function (e, text) {
                if (e.keyCode == 40 || e.keyCode == 38 || e.keyCode == 13) {
                    var current = AJS.dropDown.current;
                    if (!current) {
                        log("autoCompleteControl.onBeforeKey", "key caught before dropdown ready, ignoring");
                        return false;
                    }
                    if (current.getFocusIndex() == -1 && e.keyCode == 13) {  // user hit enter when nothing selected
                        reset();
                        return true;
                    }
                    return current.moveFocus(e);
                }
                if (e.keyCode == 27 || e.keyCode == 9 || (e.keyCode == 8 && !text)) {
                    // User has key-downed backspace but text is *already* blank - close autocomplete.
                    log("autoCompleteControl.onBeforeKey", "killing autoCompleteControl and returning false");
                    autoCompleteControl.die(e.keyCode == 8);
                    return false;
                }

                return true;
            };
            // Blocker for browser default actions for up and down keys
            autoCompleteControl.onKeyPress = function (e, text) {
                var ch = AJS.$.browser.msie ? e.keyCode : e.which,
                    character = String.fromCharCode(ch);   // charCode back to '@'
                if (e.keyCode == 40 || e.keyCode == 38 || e.keyCode == 13) {
                    tinymce.dom.Event.cancel(e);
                    return false;
                }
                var endCharIndex = AJS.indexOf(autoCompleteControl.settings.endChars,character);
                if (endCharIndex != -1) {
                    log("autoCompleteControl.onKeyPress", "caught autocomplete-closing char " + character + ", closing");
                    autoCompleteControl.die();
                }
                return true;
            };
            var twoLetters = /\S{2,}/;
            autoCompleteControl.onAfterKey = function (e, text) {
                // User deleted back to zero characters - should display default suggestions again.
                var forceUpdate = (e.keyCode == 8 && !text);
                if (forceUpdate || twoLetters.test(text)) {
                    log("onAfterKey", "Changed search string to “" + text + "”");
                    idd.change(text, forceUpdate);
                }
                return true;
            };
            autoCompleteControl.onDeath = function () {
                log("onDeath", "autoCompleteControl onDeath called");
                if (idd) {
                    idd.remove();
                    idd.closing = true;
                }
                AJS.Editor.Adapter.onHideEditor = onHideEditor;
            };

            var onHideEditor = AJS.Editor.Adapter.onHideEditor;
            AJS.Editor.Adapter.onHideEditor = function () {
                onHideEditor();
                reset();
            };
            // Start the dropdown with no text entered, to display the default suggestions.
            idd.change(autoCompleteControl.word, "force");
            return true;
        };

    var reset = function () {
        autoCompleteControl.die();
        autoCompleteControl = null;
    };

    return {

        getInputDrivenDropdown: function() {
            return idd;
        },

        // keyPress used so we can capture composite keystrokes like Sh-2 == @
        triggerListener: function(ed, e) {
            var returnValue = true,
                ch = AJS.$.browser.msie ? e.keyCode : e.which;

            if (idd) {
                // We need this listener because the autoCompleteControl's keypress listener may have been unbound by the
                // autoCompleteControl being taken down on enter *keydown*.
                if (ch == 13) { // enter
                    tinymce.dom.Event.cancel(e);
                    returnValue = false;
                }
            }
            idd && idd.closing && (idd = null);
            if (!returnValue) {
                return false;
            }

            var character = String.fromCharCode(ch);   // charCode back to '@'
            if (!idd && character in tinymce.confluence.Autocompleter.Settings) {
                log("triggerListener", "Auto-complete initiated: trigger is ", character);

                // Add the suggestion span and kill the event - we'll add the letter manually
                startAutoComplete({
                    leadingChar: character
                }) && tinymce.dom.Event.cancel(e);
            }

            return returnValue;
        },

        /**
         * Called when a Ctrl-Sh-K or Ctrl-Sh-M shortcut is fired, selects the previous word.
         *
         * Multiple shortcuts will select more previous words to narrow the search.
         */
        shortcutFired: function(leadingChar) {
            var backWords = 1;
            idd && idd.closing && (idd = null);
            if (idd) {
                backWords = autoCompleteControl.backWords + 1;
                log("shortcutFired", "autocomplete active, increasing word selection to: " + backWords);
                // the shortcut itself will be closing the previous autocomplete
                reset();
            }
            return startAutoComplete({
                leadingChar: leadingChar,
                backWords: backWords
            });
        }
    };
})(AJS.$);


