<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2012, Ajax.org B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

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

var HashHandler = require("./keyboard/hash_handler").HashHandler;
var AcePopup = require("./autocomplete/popup").AcePopup;
var util = require("./autocomplete/util");
var event = require("./lib/event");
var lang = require("./lib/lang");
var snippetManager = require("./snippets").snippetManager;

var Autocomplete = function() {
    this.autoInsert = true;
    this.autoSelect = true;
    this.keyboardHandler = new HashHandler();
    this.keyboardHandler.bindKeys(this.commands);

    this.blurListener = this.blurListener.bind(this);
    this.changeListener = this.changeListener.bind(this);
    this.mousedownListener = this.mousedownListener.bind(this);
    this.mousewheelListener = this.mousewheelListener.bind(this);

    this.changeTimer = lang.delayedCall(function() {
        this.updateCompletions(true);
    }.bind(this))
};

(function() {
    this.gatherCompletionsId = 0;

    this.$init = function() {
        this.popup = new AcePopup(document.body || document.documentElement);
        this.popup.on("click", function(e) {
            this.insertMatch();
            e.stop();
        }.bind(this));
    };

    this.openPopup = function(editor, prefix, keepPopupPosition) {
        if (!this.popup)
            this.$init();

        this.popup.setData(this.completions.filtered);

        var renderer = editor.renderer;
        this.popup.setRow(this.autoSelect ? 0 : -1);
        if (!keepPopupPosition) {
            this.popup.setTheme(editor.getTheme());
            this.popup.setFontSize(editor.getFontSize());

            var lineHeight = renderer.layerConfig.lineHeight;

            var pos = renderer.$cursorLayer.getPixelPosition(this.base, true);
            pos.left -= this.popup.getTextLeftOffset();

            var rect = editor.container.getBoundingClientRect();
            pos.top += rect.top - renderer.layerConfig.offset;
            pos.left += rect.left - editor.renderer.scrollLeft;
            pos.left += renderer.$gutterLayer.gutterWidth;

            this.popup.show(pos, lineHeight);
        }
    };

    this.detach = function() {
        this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler);
        this.editor.off("changeSelection", this.changeListener);
        this.editor.off("blur", this.blurListener);
        this.editor.off("mousedown", this.mousedownListener);
        this.editor.off("mousewheel", this.mousewheelListener);
        this.changeTimer.cancel();

        if (this.popup && this.popup.isOpen) {
            this.gatherCompletionsId = this.gatherCompletionsId + 1;
        }

        if (this.popup)
            this.popup.hide();

        this.activated = false;
        this.completions = this.base = null;
    };

    this.changeListener = function(e) {
        var cursor = this.editor.selection.lead;
        if (cursor.row != this.base.row || cursor.column < this.base.column) {
            this.detach();
        }
        if (this.activated)
            this.changeTimer.schedule();
        else
            this.detach();
    };

    this.blurListener = function() {
        if (document.activeElement != this.editor.textInput.getElement())
            this.detach();
    };

    this.mousedownListener = function(e) {
        this.detach();
    };

    this.mousewheelListener = function(e) {
        this.detach();
    };

    this.goTo = function(where) {
        var row = this.popup.getRow();
        var max = this.popup.session.getLength() - 1;

        switch(where) {
            case "up": row = row < 0 ? max : row - 1; break;
            case "down": row = row >= max ? -1 : row + 1; break;
            case "start": row = 0; break;
            case "end": row = max; break;
        }

        this.popup.setRow(row);
    };

    this.insertMatch = function(data) {
        if (!data)
            data = this.popup.getData(this.popup.getRow());
        if (!data)
            return false;

        if (data.completer && data.completer.insertMatch) {
            data.completer.insertMatch(this.editor);
        } else {
            if (this.completions.filterText) {
                var ranges = this.editor.selection.getAllRanges();
                for (var i = 0, range; range = ranges[i]; i++) {
                    range.start.column -= this.completions.filterText.length;
                    this.editor.session.remove(range);
                }
            }
            if (data.snippet)
                snippetManager.insertSnippet(this.editor, data.snippet);
            else
                this.editor.execCommand("insertstring", data.value || data);
        }
        this.detach();
    };

    this.commands = {
        "Up": function(editor) { editor.completer.goTo("up"); },
        "Down": function(editor) { editor.completer.goTo("down"); },
        "Ctrl-Up|Ctrl-Home": function(editor) { editor.completer.goTo("start"); },
        "Ctrl-Down|Ctrl-End": function(editor) { editor.completer.goTo("end"); },

        "Esc": function(editor) { editor.completer.detach(); },
        "Space": function(editor) { editor.completer.detach(); editor.insert(" ");},
        "Return": function(editor) {
            if (editor.completer.popup.getRow() == -1)
                return false;
            editor.completer.insertMatch();
        },
        "Shift-Return": function(editor) { editor.completer.insertMatch(true); },
        "Tab": function(editor) { editor.completer.insertMatch(); },

        "PageUp": function(editor) { editor.completer.popup.gotoPageUp(); },
        "PageDown": function(editor) { editor.completer.popup.gotoPageDown(); }
    };

    this.gatherCompletions = function(editor, callback) {
        var session = editor.getSession();
        var pos = editor.getCursorPosition();

        var line = session.getLine(pos.row);
        var prefix = util.retrievePrecedingIdentifier(line, pos.column);

        this.base = editor.getCursorPosition();
        this.base.column -= prefix.length;

        var matches = [];
        var total = editor.completers.length;
        editor.completers.forEach(function(completer, i) {
            completer.getCompletions(editor, session, pos, prefix, function(err, results) {
                if (!err)
                    matches = matches.concat(results);
            // Fetch prefix again, because they may have changed by now
            var pos = editor.getCursorPosition();
            var line = session.getLine(pos.row);
            callback(null, {
                    prefix: util.retrievePrecedingIdentifier(line, pos.column, results.length > 0 ? results[0].identifierRegex : undefined),
                    matches: matches,
                    finished: (--total === 0)
            });
        });
        });
        return true;
    };

    this.showPopup = function(editor) {
        if (this.editor)
            this.detach();

        this.activated = true;

        this.editor = editor;
        if (editor.completer != this) {
            if (editor.completer)
                editor.completer.detach();
            editor.completer = this;
        }

        editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
        editor.on("changeSelection", this.changeListener);
        editor.on("blur", this.blurListener);
        editor.on("mousedown", this.mousedownListener);
        editor.on("mousewheel", this.mousewheelListener);

        this.updateCompletions();
    };

    this.updateCompletions = function(keepPopupPosition) {
        if (keepPopupPosition && this.base && this.completions) {
            var pos = this.editor.getCursorPosition();
            var prefix = this.editor.session.getTextRange({start: this.base, end: pos});
            if (prefix == this.completions.filterText)
                return;
            this.completions.setFilter(prefix);
            if (!this.completions.filtered.length)
                return this.detach();
            if (this.completions.filtered.length == 1
            && this.completions.filtered[0].value == prefix
            && !this.completions.filtered[0].snippet)
                return this.detach();
            this.openPopup(this.editor, prefix, keepPopupPosition);
            return;
        }

        // Save current gatherCompletions session, session is close when a match is insert
        var _id = this.gatherCompletionsId;
        this.gatherCompletions(this.editor, function(err, results) {
            // Only detach if result gathering is finished
            var doDetach = function() {
                if (!results.finished) return;
                return this.detach();
            }.bind(this);

            // Calcul prefix
            var prefix = results.prefix;

            // Results matches
            var matches = results && results.matches;
            if (!matches || !matches.length)
                return this.detach();
            // TODO reenable this when we have proper change tracking
            // if (matches.length == 1)
            //     return this.insertMatch(matches[0]);

            if (!matches || !matches.length)
                return doDetach();

            // Wrong prefix or wrong session -> ignore
            if (prefix.indexOf(results.prefix) != 0 || _id != this.gatherCompletionsId)
                return;

            this.completions = new FilteredList(matches);
            this.completions.setFilter(prefix);
            var filtered = this.completions.filtered;

            // No results
            if (!filtered.length)
                return doDetach();

            // One result equals to the prefix
            if (filtered.length == 1 && filtered[0].value == prefix && !filtered[0].snippet)
                return doDetach();

            // Autoinsert if one result
            if (this.autoInsert && filtered.length == 1)
                return this.insertMatch(filtered[0]);

            this.openPopup(this.editor, prefix, keepPopupPosition);
        }.bind(this));
    };

    this.cancelContextMenu = function() {
        var stop = function(e) {
            this.editor.off("nativecontextmenu", stop);
            if (e && e.domEvent)
                event.stopEvent(e.domEvent);
        }.bind(this);
        setTimeout(stop, 10);
        this.editor.on("nativecontextmenu", stop);
    };

}).call(Autocomplete.prototype);

Autocomplete.startCommand = {
    name: "startAutocomplete",
    exec: function(editor) {
        if (!editor.completer)
            editor.completer = new Autocomplete();
        editor.completer.showPopup(editor);
        // needed for firefox on mac
        editor.completer.cancelContextMenu();
    },
    bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space"
};

var FilteredList = function(array, filterText, mutateData) {
    this.all = array;
    this.filtered = array;
    this.filterText = filterText || "";
};
(function(){
    this.setFilter = function(str) {
        if (str.length > this.filterText && str.lastIndexOf(this.filterText, 0) === 0)
            var matches = this.filtered;
        else
            var matches = this.all;

        this.filterText = str;
        matches = this.filterCompletions(matches, this.filterText);
        matches = matches.sort(function(a, b) {
            return b.exactMatch - a.exactMatch || b.score - a.score;
        });

        // make unique
        var prev = null;
        matches = matches.filter(function(item){
            var caption = item.value || item.caption || item.snippet;
            if (caption === prev) return false;
            prev = caption;
            return true;
        });

        this.filtered = matches;
    };
    this.filterCompletions = function(items, needle) {
        var results = [];
        var upper = needle.toUpperCase();
        var lower = needle.toLowerCase();
        loop: for (var i = 0, item; item = items[i]; i++) {
            var caption = item.value || item.caption || item.snippet;
            if (!caption) continue;
            var lastIndex = -1;
            var matchMask = 0;
            var penalty = 0;
            var index, distance;
            // caption char iteration is faster in Chrome but slower in Firefox, so lets use indexOf
            for (var j = 0; j < needle.length; j++) {
                // TODO add penalty on case mismatch
                var i1 = caption.indexOf(lower[j], lastIndex + 1);
                var i2 = caption.indexOf(upper[j], lastIndex + 1);
                index = (i1 >= 0) ? ((i2 < 0 || i1 < i2) ? i1 : i2) : i2;
                if (index < 0)
                    continue loop;
                distance = index - lastIndex - 1;
                if (distance > 0) {
                    // first char mismatch should be more sensitive
                    if (lastIndex === -1)
                        penalty += 10;
                    penalty += distance;
                }
                matchMask = matchMask | (1 << index);
                lastIndex = index;
            }
            item.matchMask = matchMask;
            item.exactMatch = penalty ? 0 : 1;
            item.score = (item.score || 0) - penalty;
            results.push(item);
        }
        return results;
    };
}).call(FilteredList.prototype);

exports.Autocomplete = Autocomplete;
exports.FilteredList = FilteredList;

});
<commit_msg>Disable Tab command binding for autocomplete (incompatible with live autocomplete)<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2012, Ajax.org B.V.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

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

var HashHandler = require("./keyboard/hash_handler").HashHandler;
var AcePopup = require("./autocomplete/popup").AcePopup;
var util = require("./autocomplete/util");
var event = require("./lib/event");
var lang = require("./lib/lang");
var snippetManager = require("./snippets").snippetManager;

var Autocomplete = function() {
    this.autoInsert = true;
    this.autoSelect = true;
    this.keyboardHandler = new HashHandler();
    this.keyboardHandler.bindKeys(this.commands);

    this.blurListener = this.blurListener.bind(this);
    this.changeListener = this.changeListener.bind(this);
    this.mousedownListener = this.mousedownListener.bind(this);
    this.mousewheelListener = this.mousewheelListener.bind(this);

    this.changeTimer = lang.delayedCall(function() {
        this.updateCompletions(true);
    }.bind(this))
};

(function() {
    this.gatherCompletionsId = 0;

    this.$init = function() {
        this.popup = new AcePopup(document.body || document.documentElement);
        this.popup.on("click", function(e) {
            this.insertMatch();
            e.stop();
        }.bind(this));
    };

    this.openPopup = function(editor, prefix, keepPopupPosition) {
        if (!this.popup)
            this.$init();

        this.popup.setData(this.completions.filtered);

        var renderer = editor.renderer;
        this.popup.setRow(this.autoSelect ? 0 : -1);
        if (!keepPopupPosition) {
            this.popup.setTheme(editor.getTheme());
            this.popup.setFontSize(editor.getFontSize());

            var lineHeight = renderer.layerConfig.lineHeight;

            var pos = renderer.$cursorLayer.getPixelPosition(this.base, true);
            pos.left -= this.popup.getTextLeftOffset();

            var rect = editor.container.getBoundingClientRect();
            pos.top += rect.top - renderer.layerConfig.offset;
            pos.left += rect.left - editor.renderer.scrollLeft;
            pos.left += renderer.$gutterLayer.gutterWidth;

            this.popup.show(pos, lineHeight);
        }
    };

    this.detach = function() {
        this.editor.keyBinding.removeKeyboardHandler(this.keyboardHandler);
        this.editor.off("changeSelection", this.changeListener);
        this.editor.off("blur", this.blurListener);
        this.editor.off("mousedown", this.mousedownListener);
        this.editor.off("mousewheel", this.mousewheelListener);
        this.changeTimer.cancel();

        if (this.popup && this.popup.isOpen) {
            this.gatherCompletionsId = this.gatherCompletionsId + 1;
        }

        if (this.popup)
            this.popup.hide();

        this.activated = false;
        this.completions = this.base = null;
    };

    this.changeListener = function(e) {
        var cursor = this.editor.selection.lead;
        if (cursor.row != this.base.row || cursor.column < this.base.column) {
            this.detach();
        }
        if (this.activated)
            this.changeTimer.schedule();
        else
            this.detach();
    };

    this.blurListener = function() {
        if (document.activeElement != this.editor.textInput.getElement())
            this.detach();
    };

    this.mousedownListener = function(e) {
        this.detach();
    };

    this.mousewheelListener = function(e) {
        this.detach();
    };

    this.goTo = function(where) {
        var row = this.popup.getRow();
        var max = this.popup.session.getLength() - 1;

        switch(where) {
            case "up": row = row < 0 ? max : row - 1; break;
            case "down": row = row >= max ? -1 : row + 1; break;
            case "start": row = 0; break;
            case "end": row = max; break;
        }

        this.popup.setRow(row);
    };

    this.insertMatch = function(data) {
        if (!data)
            data = this.popup.getData(this.popup.getRow());
        if (!data)
            return false;

        if (data.completer && data.completer.insertMatch) {
            data.completer.insertMatch(this.editor);
        } else {
            if (this.completions.filterText) {
                var ranges = this.editor.selection.getAllRanges();
                for (var i = 0, range; range = ranges[i]; i++) {
                    range.start.column -= this.completions.filterText.length;
                    this.editor.session.remove(range);
                }
            }
            if (data.snippet)
                snippetManager.insertSnippet(this.editor, data.snippet);
            else
                this.editor.execCommand("insertstring", data.value || data);
        }
        this.detach();
    };

    this.commands = {
        "Up": function(editor) { editor.completer.goTo("up"); },
        "Down": function(editor) { editor.completer.goTo("down"); },
        "Ctrl-Up|Ctrl-Home": function(editor) { editor.completer.goTo("start"); },
        "Ctrl-Down|Ctrl-End": function(editor) { editor.completer.goTo("end"); },

        "Esc": function(editor) { editor.completer.detach(); },
        "Space": function(editor) { editor.completer.detach(); editor.insert(" ");},
        "Return": function(editor) {
            if (editor.completer.popup.getRow() == -1)
                return false;
            editor.completer.insertMatch();
        },
        "Shift-Return": function(editor) { editor.completer.insertMatch(true); },

        "PageUp": function(editor) { editor.completer.popup.gotoPageUp(); },
        "PageDown": function(editor) { editor.completer.popup.gotoPageDown(); }
    };

    this.gatherCompletions = function(editor, callback) {
        var session = editor.getSession();
        var pos = editor.getCursorPosition();

        var line = session.getLine(pos.row);
        var prefix = util.retrievePrecedingIdentifier(line, pos.column);

        this.base = editor.getCursorPosition();
        this.base.column -= prefix.length;

        var matches = [];
        var total = editor.completers.length;
        editor.completers.forEach(function(completer, i) {
            completer.getCompletions(editor, session, pos, prefix, function(err, results) {
                if (!err)
                    matches = matches.concat(results);
            // Fetch prefix again, because they may have changed by now
            var pos = editor.getCursorPosition();
            var line = session.getLine(pos.row);
            callback(null, {
                    prefix: util.retrievePrecedingIdentifier(line, pos.column, results.length > 0 ? results[0].identifierRegex : undefined),
                    matches: matches,
                    finished: (--total === 0)
            });
        });
        });
        return true;
    };

    this.showPopup = function(editor) {
        if (this.editor)
            this.detach();

        this.activated = true;

        this.editor = editor;
        if (editor.completer != this) {
            if (editor.completer)
                editor.completer.detach();
            editor.completer = this;
        }

        editor.keyBinding.addKeyboardHandler(this.keyboardHandler);
        editor.on("changeSelection", this.changeListener);
        editor.on("blur", this.blurListener);
        editor.on("mousedown", this.mousedownListener);
        editor.on("mousewheel", this.mousewheelListener);

        this.updateCompletions();
    };

    this.updateCompletions = function(keepPopupPosition) {
        if (keepPopupPosition && this.base && this.completions) {
            var pos = this.editor.getCursorPosition();
            var prefix = this.editor.session.getTextRange({start: this.base, end: pos});
            if (prefix == this.completions.filterText)
                return;
            this.completions.setFilter(prefix);
            if (!this.completions.filtered.length)
                return this.detach();
            if (this.completions.filtered.length == 1
            && this.completions.filtered[0].value == prefix
            && !this.completions.filtered[0].snippet)
                return this.detach();
            this.openPopup(this.editor, prefix, keepPopupPosition);
            return;
        }

        // Save current gatherCompletions session, session is close when a match is insert
        var _id = this.gatherCompletionsId;
        this.gatherCompletions(this.editor, function(err, results) {
            // Only detach if result gathering is finished
            var doDetach = function() {
                if (!results.finished) return;
                return this.detach();
            }.bind(this);

            // Calcul prefix
            var prefix = results.prefix;

            // Results matches
            var matches = results && results.matches;
            if (!matches || !matches.length)
                return this.detach();
            // TODO reenable this when we have proper change tracking
            // if (matches.length == 1)
            //     return this.insertMatch(matches[0]);

            if (!matches || !matches.length)
                return doDetach();

            // Wrong prefix or wrong session -> ignore
            if (prefix.indexOf(results.prefix) != 0 || _id != this.gatherCompletionsId)
                return;

            this.completions = new FilteredList(matches);
            this.completions.setFilter(prefix);
            var filtered = this.completions.filtered;

            // No results
            if (!filtered.length)
                return doDetach();

            // One result equals to the prefix
            if (filtered.length == 1 && filtered[0].value == prefix && !filtered[0].snippet)
                return doDetach();

            // Autoinsert if one result
            if (this.autoInsert && filtered.length == 1)
                return this.insertMatch(filtered[0]);

            this.openPopup(this.editor, prefix, keepPopupPosition);
        }.bind(this));
    };

    this.cancelContextMenu = function() {
        var stop = function(e) {
            this.editor.off("nativecontextmenu", stop);
            if (e && e.domEvent)
                event.stopEvent(e.domEvent);
        }.bind(this);
        setTimeout(stop, 10);
        this.editor.on("nativecontextmenu", stop);
    };

}).call(Autocomplete.prototype);

Autocomplete.startCommand = {
    name: "startAutocomplete",
    exec: function(editor) {
        if (!editor.completer)
            editor.completer = new Autocomplete();
        editor.completer.showPopup(editor);
        // needed for firefox on mac
        editor.completer.cancelContextMenu();
    },
    bindKey: "Ctrl-Space|Ctrl-Shift-Space|Alt-Space"
};

var FilteredList = function(array, filterText, mutateData) {
    this.all = array;
    this.filtered = array;
    this.filterText = filterText || "";
};
(function(){
    this.setFilter = function(str) {
        if (str.length > this.filterText && str.lastIndexOf(this.filterText, 0) === 0)
            var matches = this.filtered;
        else
            var matches = this.all;

        this.filterText = str;
        matches = this.filterCompletions(matches, this.filterText);
        matches = matches.sort(function(a, b) {
            return b.exactMatch - a.exactMatch || b.score - a.score;
        });

        // make unique
        var prev = null;
        matches = matches.filter(function(item){
            var caption = item.value || item.caption || item.snippet;
            if (caption === prev) return false;
            prev = caption;
            return true;
        });

        this.filtered = matches;
    };
    this.filterCompletions = function(items, needle) {
        var results = [];
        var upper = needle.toUpperCase();
        var lower = needle.toLowerCase();
        loop: for (var i = 0, item; item = items[i]; i++) {
            var caption = item.value || item.caption || item.snippet;
            if (!caption) continue;
            var lastIndex = -1;
            var matchMask = 0;
            var penalty = 0;
            var index, distance;
            // caption char iteration is faster in Chrome but slower in Firefox, so lets use indexOf
            for (var j = 0; j < needle.length; j++) {
                // TODO add penalty on case mismatch
                var i1 = caption.indexOf(lower[j], lastIndex + 1);
                var i2 = caption.indexOf(upper[j], lastIndex + 1);
                index = (i1 >= 0) ? ((i2 < 0 || i1 < i2) ? i1 : i2) : i2;
                if (index < 0)
                    continue loop;
                distance = index - lastIndex - 1;
                if (distance > 0) {
                    // first char mismatch should be more sensitive
                    if (lastIndex === -1)
                        penalty += 10;
                    penalty += distance;
                }
                matchMask = matchMask | (1 << index);
                lastIndex = index;
            }
            item.matchMask = matchMask;
            item.exactMatch = penalty ? 0 : 1;
            item.score = (item.score || 0) - penalty;
            results.push(item);
        }
        return results;
    };
}).call(FilteredList.prototype);

exports.Autocomplete = Autocomplete;
exports.FilteredList = FilteredList;

});
<|endoftext|>


<commit_before>var React = require("react")
var {div, span, label} = React.DOM
var cx = require("classnames")

module.exports = {
  errorList(errors) {
    let i = 0
    return (errors || []).map( (msg) => module.exports.error(msg, i++))
  },

  error(msg, i=0) {
    return span({className: "help-block", key: `error-${i}`},
      React.DOM.i({className: "fa fa-exclamation-circle"}, ` ${msg}`),
    )
  },

  label(props, overrides = {}) {
    let horizontalClasses = sizeClassNames(props.labelWidth, {offsets: false})
    if (!props.label || props.block) {
      if (props.layout === "horizontal" && !props.block) {
        return div({className: horizontalClasses})
      }
      else {
        return ""
      }
    }
    let labelHtml = Object.assign({}, props, overrides)
    labelHtml.className = cx(labelHtml.className, {
      [horizontalClasses]: props.layout === "horizontal",
    })

    return div({}, label(labelHtml, props.label))
  },

  savedText({saved, className}) {
    className = className || "frigb-saved pull-right"
    if (!saved) return ""
    return span({className, key: "saved"}, "saved")
  },

  savedNotification({label, saved, className}) {
    className = cx({
      className: className !== undefined,
      "frigb-saved-inline": label === undefined,
    })

    return savedText({saved, className})
  },

  inputContainerCx(props) {
    let labelWidth = props.labelWidth || {}
    let inputWidth = {}
    // The width of the input is the number of columns left after the label
    // is placed on the row. If the label takes a full row (12 coluns) then
    // the input is given another row (12 columns wide).
    for (let k in labelWidth) inputWidth[k] = (12 - labelWidth[k]) || 12
    let horizontalClasses = sizeClassNames(inputWidth, {offsets: false})
    return cx({
      [horizontalClasses]: props.layout === "horizontal" && !props.block,
      ["col-xs-12"]: props.layout === "horizontal" && props.block,
    })
  },

  sizeClassNames(props = {}, opts = {offsets: true}) {
    var classes = {}
    var sizes = ["xs", "sm", "md", "lg"]
    // Adding column classes
    for (var k of sizes) {
      if (props[k] != null) classes[`col-${k}-${props[k]}`] = true
    }
    // Adding offset classes
    if (opts.offsets) {
      for (var size of sizes) {
        k = `${size}Offset`
        if (props[k] == null) continue
        classes[`col-${size}-offset-${props[k]}`] = true
      }
    }
    return cx(classes)
  },

  formGroupCx(props, overrides = {}) {
    let isCheckbox = props.inputHtml.type === "checkbox"
    return cx(Object.assign({
      "form-group": !isCheckbox,
      "checkbox": isCheckbox,
      "has-error": props.errors != null,
      "has-success": props.modified && props.errors == null,
    }, overrides))
  },

}

var {savedText, sizeClassNames} = module.exports
<commit_msg>Add Props From Component To Notification<commit_after>var React = require("react")
var {div, span, label} = React.DOM
var cx = require("classnames")

module.exports = {
  errorList(errors) {
    let i = 0
    return (errors || []).map( (msg) => module.exports.error(msg, i++))
  },

  error(msg, i=0) {
    return span({className: "help-block", key: `error-${i}`},
      React.DOM.i({className: "fa fa-exclamation-circle"}, ` ${msg}`),
    )
  },

  label(props, overrides = {}) {
    let horizontalClasses = sizeClassNames(props.labelWidth, {offsets: false})
    if (!props.label || props.block) {
      if (props.layout === "horizontal" && !props.block) {
        return div({className: horizontalClasses})
      }
      else {
        return ""
      }
    }
    let labelHtml = Object.assign({}, props, overrides)
    labelHtml.className = cx(labelHtml.className, {
      [horizontalClasses]: props.layout === "horizontal",
    })

    return div({}, label(labelHtml, props.label))
  },

  savedText({saved, className}) {
    className = className || "frigb-saved pull-right"
    if (!saved) return ""
    return span({className, key: "saved"}, "saved")
  },

  savedNotification({parentProps, label, overrideSaved, className}) {
    let isSaved = overrideSaved || parentProps.saved && parentProps.modified

    className = cx({
      className: className !== undefined,
      "frigb-saved-inline": label === undefined,
    })

    return savedText({isSaved, className})
  },

  inputContainerCx(props) {
    let labelWidth = props.labelWidth || {}
    let inputWidth = {}
    // The width of the input is the number of columns left after the label
    // is placed on the row. If the label takes a full row (12 coluns) then
    // the input is given another row (12 columns wide).
    for (let k in labelWidth) inputWidth[k] = (12 - labelWidth[k]) || 12
    let horizontalClasses = sizeClassNames(inputWidth, {offsets: false})
    return cx({
      [horizontalClasses]: props.layout === "horizontal" && !props.block,
      ["col-xs-12"]: props.layout === "horizontal" && props.block,
    })
  },

  sizeClassNames(props = {}, opts = {offsets: true}) {
    var classes = {}
    var sizes = ["xs", "sm", "md", "lg"]
    // Adding column classes
    for (var k of sizes) {
      if (props[k] != null) classes[`col-${k}-${props[k]}`] = true
    }
    // Adding offset classes
    if (opts.offsets) {
      for (var size of sizes) {
        k = `${size}Offset`
        if (props[k] == null) continue
        classes[`col-${size}-offset-${props[k]}`] = true
      }
    }
    return cx(classes)
  },

  formGroupCx(props, overrides = {}) {
    let isCheckbox = props.inputHtml.type === "checkbox"
    return cx(Object.assign({
      "form-group": !isCheckbox,
      "checkbox": isCheckbox,
      "has-error": props.errors != null,
      "has-success": props.modified && props.errors == null,
    }, overrides))
  },

}

var {savedText, sizeClassNames} = module.exports
<|endoftext|>


<commit_before>/** DRAG N DROP FRAMEWORK
 ****************************************************************************/

var mousedown = false,
    mousedrag = false,
    sourceElem = null, // the REAL dragged element
    dragElem = null, // dragged element copy
    timeOfMouseDown = null;
    dropCallbacks = [],
    MOUSE_DRAG_TIMEOUT = 100, // Milliseconds until drag kicks in
    mouseDraggedCallback = null;

function registerDropCallback(f) {
    dropCallbacks.push(f);
}

function removeTargetClasses() {
    $('.target').removeClass('target');
    $('.insert-before').removeClass('insert-before')
    $('.insert-after').removeClass('insert-after');
    $('.alert').removeClass('alert');
}

function dragAborted() {
    if (dragElem) {
        dragElem.remove();
        dragElem = null;
    }
    removeTargetClasses();
}

/**
 * If the drag element has a child element with class 'title', this will
 * be used as text for the ghost element.
 */
function dragStarted(event) {
    sourceElem = findDraggable($(event.target)); // set global
	var text = '';
    if (sourceElem.find('.title').length) {
		var titles = sourceElem.find('.title').not(sourceElem.find('.alternative > .title'));
		$.each(titles, function(index, item) {
			text += $(item).text();
			if (index < titles.length-1)
				text += '
';
		});
	} else {
		text = sourceElem.text();
	}
	dragElem = $('')
        .html(text)
        .appendTo('body');
}

function dragEnded(event) {
    var droppable = findDroppable($(event.target)),
        i;

    if (droppable) {
        for (i = 0; i < dropCallbacks.length; i += 1) {
            dropCallbacks[i](dragElem, sourceElem, droppable);
        }
    }

    removeTargetClasses();

    if (dragElem) { // todo: fix, dragElem should always exist?
        dragElem.remove();
        dragElem = null;
    }
}

/**
 * Returns the droppable element, either the element itself
 * or any of its parents
 */
function findDroppable(elem) {
    var droppable;
    if (elem.hasClass('droppable')) {
        droppable = elem;
    } else {
        droppable = elem.parents('.droppable');
        if (droppable) {
            droppable = $(droppable[0]);
        }
    }
    return droppable;
}

function findDraggable(elem) {
    var draggable;
    if (elem.hasClass('draggable')) {
        draggable = elem;
    } else {
        draggable = elem.parents('.draggable');
        if (draggable) {
            draggable = $(draggable[0]);
        }
    }
	if (draggable.hasClass('selected')) {
		draggable = draggable.parent().children('.selected');
	} else {
		selectVideo(draggable);
	}
    return draggable;
}

function mouseDragged(event) {
    var target = $(event.target);
    var droppable = findDroppable(target);

    if (!dragElem) { // really needed?
        return;
    }

    dragElem.css({
        'top': event.pageY,
        'left': event.pageX + 10
    });

    removeTargetClasses();

    if (mouseDraggedCallback) {
        mouseDraggedCallback(droppable, sourceElem);
    }

    if (droppable) {
        if (sourceElem.data('type') !== undefined) {
            if (droppable.data('type') !== sourceElem.data('type')) {
                droppable.addClass('target');
            }
        } else {
            droppable.addClass('target');
        }

        if (droppable.hasClass('reorderable') && droppable.data('type') === sourceElem.data('type')) {
            droppable.addClass('insert-before');
        }
    }
}

$(window).mousemove(function (event) {
    if (!mousedrag && mousedown && ($(event.target).hasClass('draggable') || $(event.target).parents('.draggable').length > 0)) {
        var now = new Date().getTime();
        if ((now - timeOfMouseDown) >= MOUSE_DRAG_TIMEOUT) {
            mousedrag = true;
            dragStarted(event);
        }
    }

    if (mousedrag) {
        mouseDragged(event);
    }
});

$(window).mouseup(function (event) {
    $('body').removeClass('mousedrag');
    mousedown = false;

    if (mousedrag) {
        mousedrag = false;
        dragEnded(event);
    }
});

$(window).mousedown(function (event) {
    timeOfMouseDown = new Date().getTime();
    if ($(event.target).hasClass('draggable') ||
		$(event.target).parents('.draggable') ) {
        mousedown = true;
    }
});

$(window).keyup(function (event) {
    if (event.keyCode === 27) { // ESC
        dragAborted();
    }
});

/** DRAG N DROP CALLBACKS
 ****************************************************************************/

mouseDraggedCallback = function(targetElem, sourceElem) {
    if (targetElem.attr('id') === 'results-container' && sourceElem.hasClass('video')) {
        $('#playlist .video:last').addClass('insert-after');
    }
}

// VIDEO DROPPED ON #results-container
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    var playlistElem,
        destIndex,
        sourceIndex,
        playlist;

    if ($('#playlists .selected').length) {
        playlistElem = $('.playlistElem.selected');
        if (targetElem.attr('id') === 'results-container' && sourceElem.hasClass('video') && playlistElem.length) {
            sourceIndex = sourceElem.index();
            destIndex = $('#playlist .video:last').index() + 1;

            playlist = playlistElem.data('model');
            playlist.moveVideo(sourceIndex, destIndex);
            playlistManager.save();

            playlistElem.click(); // trigger a rerender
        }
    }
});

// VIDEO DROPPED ON PLAYLIST
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    if (targetElem.hasClass('playlistElem') && sourceElem.hasClass('video')) {
        var playlist = targetElem.data('model');
        $.each(sourceElem, function(index, item) {
            item = $(item);
            if (item.hasClass('video')) {
                playlist.addVideo(item.find('.title').text(), item.data('videoId'));
                item.removeClass('selected');
            }
        });
        playlistManager.save();
        constructPlaylistsMenu();
    }
});

// VIDEO DROPPED ON #new-playlist
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    if (targetElem.attr('id') === 'new-playlist' && sourceElem.hasClass('video')) {
        pendingVideo = {
            title: sourceElem.text(),
            videoId: sourceElem.data('videoId')
        };
        $('#new-playlist span').click();
    }
});

// VIDEO TITLE DROPPED ON #new-playlist
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    if (targetElem.attr('id') === 'new-playlist' && sourceElem.attr('id') === 'info') {
        pendingVideo = {
            title: sourceElem.text(),
            videoId: Player.getCurrentVideoId()
        };
        $('#new-playlist span').click();
    }
});

// VIDEO TITLE DROPPED ON PLAYLIST
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    if (targetElem.hasClass('playlistElem') && sourceElem.attr('id') === 'info') {
        var playlist = targetElem.data('model');
        playlist.addVideo(sourceElem.text(), Player.getCurrentVideoId());
        playlistManager.save();
        constructPlaylistsMenu();
    }
});

// VIDEO DROPPED ON ANOTHER VIDEO
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    var playlistElem = $('.playlistElem.selected');
    var playlist = playlistElem.data('model');
    if (targetElem.hasClass('video') && sourceElem.hasClass('video') && playlistElem.length) {
        playlist.moveVideo(sourceElem.index(), targetElem.index());
        playlistManager.save();
        playlistElem.click();
    }
});
<commit_msg>Fix possible video reordering bug<commit_after>/** DRAG N DROP FRAMEWORK
 ****************************************************************************/

var mousedown = false,
    mousedrag = false,
    sourceElem = null, // the REAL dragged element
    dragElem = null, // dragged element copy
    timeOfMouseDown = null;
    dropCallbacks = [],
    MOUSE_DRAG_TIMEOUT = 100, // Milliseconds until drag kicks in
    mouseDraggedCallback = null;

function registerDropCallback(f) {
    dropCallbacks.push(f);
}

function removeTargetClasses() {
    $('.target').removeClass('target');
    $('.insert-before').removeClass('insert-before')
    $('.insert-after').removeClass('insert-after');
    $('.alert').removeClass('alert');
}

function dragAborted() {
    if (dragElem) {
        dragElem.remove();
        dragElem = null;
    }
    removeTargetClasses();
}

/**
 * If the drag element has a child element with class 'title', this will
 * be used as text for the ghost element.
 */
function dragStarted(event) {
    sourceElem = findDraggable($(event.target)); // set global
	var text = '';
    if (sourceElem.find('.title').length) {
		var titles = sourceElem.find('.title').not(sourceElem.find('.alternative > .title'));
		$.each(titles, function(index, item) {
			text += $(item).text();
			if (index < titles.length-1)
				text += '
';
		});
	} else {
		text = sourceElem.text();
	}
	dragElem = $('')
        .html(text)
        .appendTo('body');
}

function dragEnded(event) {
    var droppable = findDroppable($(event.target)),
        i;

    if (droppable) {
        for (i = 0; i < dropCallbacks.length; i += 1) {
            dropCallbacks[i](dragElem, sourceElem, droppable);
        }
    }

    removeTargetClasses();

    if (dragElem) { // todo: fix, dragElem should always exist?
        dragElem.remove();
        dragElem = null;
    }
}

/**
 * Returns the droppable element, either the element itself
 * or any of its parents
 */
function findDroppable(elem) {
    var droppable;
    if (elem.hasClass('droppable')) {
        droppable = elem;
    } else {
        droppable = elem.parents('.droppable');
        if (droppable) {
            droppable = $(droppable[0]);
        }
    }
    return droppable;
}

function findDraggable(elem) {
    var draggable;
    if (elem.hasClass('draggable')) {
        draggable = elem;
    } else {
        draggable = elem.parents('.draggable');
        if (draggable) {
            draggable = $(draggable[0]);
        }
    }
	if (draggable.hasClass('selected')) {
		draggable = draggable.parent().children('.selected');
	} else {
		selectVideo(draggable);
	}
    return draggable;
}

function mouseDragged(event) {
    var target = $(event.target);
    var droppable = findDroppable(target);

    if (!dragElem) { // really needed?
        return;
    }

    dragElem.css({
        'top': event.pageY,
        'left': event.pageX + 10
    });

    removeTargetClasses();

    if (mouseDraggedCallback) {
        mouseDraggedCallback(droppable, sourceElem);
    }

    if (droppable) {
        if (sourceElem.data('type') !== undefined) {
            if (droppable.data('type') !== sourceElem.data('type')) {
                droppable.addClass('target');
            }
        } else {
            droppable.addClass('target');
        }

        if (droppable.hasClass('reorderable') && droppable.data('type') === sourceElem.data('type')) {
            droppable.addClass('insert-before');
        }
    }
}

$(window).mousemove(function (event) {
    if (!mousedrag && mousedown && ($(event.target).hasClass('draggable') || $(event.target).parents('.draggable').length > 0)) {
        var now = new Date().getTime();
        if ((now - timeOfMouseDown) >= MOUSE_DRAG_TIMEOUT) {
            mousedrag = true;
            dragStarted(event);
        }
    }

    if (mousedrag) {
        mouseDragged(event);
    }
});

$(window).mouseup(function (event) {
    $('body').removeClass('mousedrag');
    mousedown = false;

    if (mousedrag) {
        mousedrag = false;
        dragEnded(event);
    }
});

$(window).mousedown(function (event) {
    timeOfMouseDown = new Date().getTime();
    if ($(event.target).hasClass('draggable') ||
		$(event.target).parents('.draggable') ) {
        mousedown = true;
    }
});

$(window).keyup(function (event) {
    if (event.keyCode === 27) { // ESC
        dragAborted();
    }
});

/** DRAG N DROP CALLBACKS
 ****************************************************************************/

mouseDraggedCallback = function(targetElem, sourceElem) {
    if (targetElem.attr('id') === 'results-container' && sourceElem.hasClass('video')) {
        $('#playlist .video:last').addClass('insert-after');
    }
}

// VIDEO DROPPED ON #results-container
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    var playlistElem,
        destIndex,
        sourceIndex,
        playlist;

    if ($('#playlists .selected').length) {
        playlistElem = $('.playlistElem.selected');
        if (targetElem.attr('id') === 'results-container' && sourceElem.hasClass('video') && playlistElem.length) {
            sourceIndex = sourceElem.index();
            destIndex = $('#playlist .video:last').index() + 1;

            playlist = playlistElem.data('model');
            playlist.moveVideo(sourceIndex, destIndex);
            playlistManager.save();

            playlistElem.click(); // trigger a rerender
        }
    }
});

// VIDEO DROPPED ON PLAYLIST
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    if (targetElem.hasClass('playlistElem') && sourceElem.hasClass('video')) {
        var playlist = targetElem.data('model');
        $.each(sourceElem, function(index, item) {
            item = $(item);
            if (item.hasClass('video')) {
                playlist.addVideo(item.find('.title').text(), item.data('videoId'));
                item.removeClass('selected');
            }
        });
        playlistManager.save();
        constructPlaylistsMenu();
    }
});

// VIDEO DROPPED ON #new-playlist
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    if (targetElem.attr('id') === 'new-playlist' && sourceElem.hasClass('video')) {
        pendingVideo = {
            title: sourceElem.text(),
            videoId: sourceElem.data('videoId')
        };
        $('#new-playlist span').click();
    }
});

// VIDEO TITLE DROPPED ON #new-playlist
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    if (targetElem.attr('id') === 'new-playlist' && sourceElem.attr('id') === 'info') {
        pendingVideo = {
            title: sourceElem.text(),
            videoId: Player.getCurrentVideoId()
        };
        $('#new-playlist span').click();
    }
});

// VIDEO TITLE DROPPED ON PLAYLIST
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    if (targetElem.hasClass('playlistElem') && sourceElem.attr('id') === 'info') {
        var playlist = targetElem.data('model');
        playlist.addVideo(sourceElem.text(), Player.getCurrentVideoId());
        playlistManager.save();
        constructPlaylistsMenu();
    }
});

// VIDEO DROPPED ON ANOTHER VIDEO
registerDropCallback(function (dragElem, sourceElem, targetElem) {
    var playlist = $('#playlistbar').data('playlist');
    if (targetElem.hasClass('video') && sourceElem.hasClass('video')) {
        playlist.moveVideo(sourceElem.index(), targetElem.index());
        playlistManager.save();
        loadPlaylistView(playlist);
    }
});
<|endoftext|>


<commit_before>CodeMirror.defineMode("karel-en", function(conf) {
    var ERRORCLASS = 'ka-error';

    function wordRegexp(words) {
        return new RegExp("^((" + words.join(")|(") + "))\\b");
    }

    var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
    var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
    var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
    var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
    var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
    var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");

    var wordOperators = wordRegexp(['not', 'and', 'or']);
    var commonkeywords = ['go', 'left', 'right', 'put', 'get', 'repeat',
    'while', 'if', 'else', 'def'];
    var commontypes = ['home', 'north', 'empty', 'acid', 'anchor', 'bone', 'box', 'bush', 'cactus', 'corn', 'fire', 'gem', 'jellyfish', 'mark', 'mine', 'palm', 'plant', 'radiation', 'rock', 'scorpion', 'skull', 'stone', 'stump', 'tomb', 'torpedo', 'tray', 'tree', 'wall', 'water', 'wood', 'bag', 'baket', 'cart', 'chest', 'vault', 'net', 'bin', 'trash', 'toilet', 'bowl', 'nugget', 'pumpkin', 'spider', 'snake', 'rose', 'tulip', 'apple', 'cherry', 'eye', 'brain', 'candy', 'book', 'key', 'pie', 'gears', 'coin', 'gun', 'ammo', 'grenade', 'pill', 'medkit', 'radio', 'bomb', 'starfish', 'pearl', 'crab', 'shoe', 'beads', 'ribbon', 'ring', 'map', 'hat', 'pipe', 'lens', 'phone', 'watch', 'rug', 'shield', 'mask', 'ball', 'orchid', 'sword', 'chip', 'basket', 'lollipop', 'table', 'chair'];
    var commonBlockKeywords = ['repeat', 'while', 'if', 'else', 'def'];
    var ka2 = {
        'types': ['basestring', 'buffer', 'file', 'long', 'unicode',
        'xrange'],
        'keywords': ['exec', 'print']
        };
    var ka3 = {
        'types': ['bytearray', 'bytes', 'filter', 'map', 'memoryview',
        'open', 'range', 'zip'],
        'keywords': ['nonlocal']
        };
    var stringPrefixes;

    if (!!conf.mode.version && parseInt(conf.mode.version, 10) === 3) {
        commonkeywords = commonkeywords.concat(ka3.keywords);
        commontypes = commontypes.concat(ka3.types);
        stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
    } else {
        commonkeywords = commonkeywords.concat(ka2.keywords);
        commontypes = commontypes.concat(ka2.types);
        stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
    }
    var keywords = wordRegexp(commonkeywords);
    var types = wordRegexp(commontypes);
    var blockKeywords = new RegExp("^(" + commonBlockKeywords.join("|") + ")$");

    // tokenizers
    function tokenBase(stream, state) {
        // Handle scope changes
        if (stream.sol()) {
            var scopeOffset = state.scopes[0].offset;
            if (stream.eatSpace()) {
                var lineOffset = stream.indentation();
                if (lineOffset > scopeOffset) {
                    return 'ka-indent';
                } else if (lineOffset < scopeOffset) {
                    return 'ka-dedent';
                }
                return 'whitespace';
            } else {
                if (scopeOffset > 0) {
                    dedent(stream, state);
                }
            }
        }
        if (stream.eatSpace()) {
            return 'ka-space';
        }

        var ch = stream.peek();

        // Handle Comments
        if (ch === '#') {
            stream.skipToEnd();
            return 'ka-comment';
        }

        // Handle Number Literals
        if (stream.match(/^[0-9\.]/, false)) {
            var floatLiteral = false;
            // Floats
            if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) {
                floatLiteral = true;
            }
            if (stream.match(/^\d+\.\d*/)) {
                floatLiteral = true;
            }
            if (stream.match(/^\.\d+/)) {
                floatLiteral = true;
            }
            if (floatLiteral) {
                // Float literals may be "imaginary"
                stream.eat(/J/i);
                return 'ka-literal';
            }
            // Integers
            var intLiteral = false;
            // Hex
            if (stream.match(/^0x[0-9a-f]+/i)) {
                intLiteral = true;
            }
            // Binary
            if (stream.match(/^0b[01]+/i)) {
                intLiteral = true;
            }
            // Octal
            if (stream.match(/^0o[0-7]+/i)) {
                intLiteral = true;
            }
            // Decimal
            if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
                // Decimal literals may be "imaginary"
                stream.eat(/J/i);
                // TODO - Can you have imaginary longs?
                intLiteral = true;
            }
            // Zero by itself with no other piece of number.
            if (stream.match(/^0(?![\dx])/i)) {
                intLiteral = true;
            }
            if (intLiteral) {
                // Integer literals may be "long"
                stream.eat(/L/i);
                return 'ka-literal';
            }
        }

        // Handle Strings
        if (stream.match(stringPrefixes)) {
            state.tokenize = tokenStringFactory(stream.current());
            return state.tokenize(stream, state);
        }

        // Handle operators and Delimiters
        if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
            return 'ka-delimiter';
        }
        if (stream.match(doubleOperators)
            || stream.match(singleOperators)
            || stream.match(wordOperators)) {
            return 'ka-operator';
        }
        if (stream.match(singleDelimiters)) {
            return 'ka-delimiter';
        }

        if (stream.match(types)) {
            return 'ka-type';
        }

        if (stream.match(keywords)) {
            return 'ka-keyword';
        }

        if (stream.match(identifiers)) {
            return 'ka-identifier';
        }

        // Handle non-detected items
        stream.next();
        return ERRORCLASS;
    }

    function tokenStringFactory(delimiter) {
        while ('rub'.indexOf(delimiter[0].toLowerCase()) >= 0) {
            delimiter = delimiter.substr(1);
        }
        var delim_re = new RegExp(delimiter);
        var singleline = delimiter.length == 1;
        var OUTCLASS = 'ka-string';

        return function tokenString(stream, state) {
            while (!stream.eol()) {
                stream.eatWhile(/[^'"\\]/);
                if (stream.eat('\\')) {
                    stream.next();
                    if (singleline && stream.eol()) {
                        return OUTCLASS;
                    }
                } else if (stream.match(delim_re)) {
                    state.tokenize = tokenBase;
                    return OUTCLASS;
                } else {
                    stream.eat(/['"]/);
                }
            }
            if (singleline) {
                if (conf.mode.singleLineStringErrors) {
                    OUTCLASS = ERRORCLASS
                } else {
                    state.tokenize = tokenBase;
                }
            }
            return OUTCLASS;
        };
    }

    function indent(stream, state, type) {
        type = type || 'py';
        var indentUnit = 0;
        if (type === 'py') {
            for (var i = 0; i < state.scopes.length; ++i) {
                if (state.scopes[i].type === 'py') {
                    indentUnit = state.scopes[i].offset + conf.indentUnit;
                    break;
                }
            }
        } else {
            indentUnit = stream.column() + stream.current().length;
        }
        state.scopes.unshift({
            offset: indentUnit,
            type: type
        });
    }

    function dedent(stream, state) {
        if (state.scopes.length == 1) return;
        if (state.scopes[0].type === 'py') {
            var _indent = stream.indentation();
            var _indent_index = -1;
            for (var i = 0; i < state.scopes.length; ++i) {
                if (_indent === state.scopes[i].offset) {
                    _indent_index = i;
                    break;
                }
            }
            if (_indent_index === -1) {
                return true;
            }
            while (state.scopes[0].offset !== _indent) {
                state.scopes.shift();
            }
            return false
        } else {
            state.scopes.shift();
            return false;
        }
    }

    function tokenLexer(stream, state) {
        var style = state.tokenize(stream, state);
        var current = stream.current();

        // Handle '.' connected identifiers
        if (current === '.') {
            style = state.tokenize(stream, state);
            current = stream.current();
            if (style === 'ka-identifier') {
                return 'ka-identifier';
            } else {
                return ERRORCLASS;
            }
        }

        // Handle decorators
        if (current === '@') {
            style = state.tokenize(stream, state);
            current = stream.current();
            if (style === 'ka-identifier'
                || current === '@staticmethod'
                || current === '@classmethod') {
                return 'ka-decorator';
            } else {
                return ERRORCLASS;
            }
        }

        // Handle scope changes.
        if (current === 'pass' || current === 'return') {
            state.dedent += 1;
        }
        if ((stream.string.slice(stream.indentation(), stream.pos).match(blockKeywords)
            && !state.lambda && state.scopes[0].type == 'py')
            || style === 'ka-indent') {
            indent(stream, state);
        }
        var delimiter_index = '[({'.indexOf(current);
        if (delimiter_index !== -1) {
            indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
        }
        if (style === 'ka-dedent') {
            if (dedent(stream, state)) {
                return ERRORCLASS;
            }
        }
        delimiter_index = '])}'.indexOf(current);
        if (delimiter_index !== -1) {
            if (dedent(stream, state)) {
                return ERRORCLASS;
            }
        }
        if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') {
            if (state.scopes.length > 1) state.scopes.shift();
            state.dedent -= 1;
        }

        return style;
    }

    var external = {
        startState: function(basecolumn) {
            return {
                tokenize: tokenBase,
                scopes: [{
                    offset:basecolumn || 0,
                    type:'py'
                }],
                lastToken: null,
                lambda: false,
                dedent: 0
            };
        },

        token: function(stream, state) {
            var style = tokenLexer(stream, state);

            state.lastToken = {
                style:style,
                content: stream.current()
                };

            if (stream.eol() && stream.lambda) {
                state.lambda = false;
            }

            return style;
        },

        indent: function(state, textAfter) {
            if (state.tokenize != tokenBase) {
                return 0;
            }

            return state.scopes[0].offset;
        }

    };
    return external;
});

CodeMirror.defineMIME("text/x-karel", "karel-en");
<commit_msg>new keywords: barrel, oil, baseball, tools<commit_after>CodeMirror.defineMode("karel-en", function(conf) {
    var ERRORCLASS = 'ka-error';

    function wordRegexp(words) {
        return new RegExp("^((" + words.join(")|(") + "))\\b");
    }

    var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!]");
    var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]');
    var doubleOperators = new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))");
    var doubleDelimiters = new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))");
    var tripleDelimiters = new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))");
    var identifiers = new RegExp("^[_A-Za-z][_A-Za-z0-9]*");

    var wordOperators = wordRegexp(['not', 'and', 'or']);
    var commonkeywords = ['go', 'left', 'right', 'put', 'get', 'repeat',
    'while', 'if', 'else', 'def'];
    var commontypes = ['home', 'north', 'empty', 'acid', 'anchor', 'bone', 'box', 'bush', 'cactus', 'corn', 'fire', 'gem', 'jellyfish', 'mark', 'mine', 'palm', 'plant', 'radiation', 'rock', 'scorpion', 'skull', 'stone', 'stump', 'tomb', 'torpedo', 'tray', 'tree', 'wall', 'water', 'wood', 'bag', 'baket', 'cart', 'chest', 'vault', 'net', 'bin', 'trash', 'toilet', 'bowl', 'nugget', 'pumpkin', 'spider', 'snake', 'rose', 'tulip', 'apple', 'cherry', 'eye', 'brain', 'candy', 'book', 'key', 'pie', 'gears', 'coin', 'gun', 'ammo', 'grenade', 'pill', 'medkit', 'radio', 'bomb', 'starfish', 'pearl', 'crab', 'shoe', 'beads', 'ribbon', 'ring', 'map', 'hat', 'pipe', 'lens', 'phone', 'watch', 'rug', 'shield', 'mask', 'ball', 'orchid', 'sword', 'chip', 'basket', 'lollipop', 'table', 'chair', 'barrel', 'oil', 'baseball', 'tools'];
    var commonBlockKeywords = ['repeat', 'while', 'if', 'else', 'def'];
    var ka2 = {
        'types': ['basestring', 'buffer', 'file', 'long', 'unicode',
        'xrange'],
        'keywords': ['exec', 'print']
        };
    var ka3 = {
        'types': ['bytearray', 'bytes', 'filter', 'map', 'memoryview',
        'open', 'range', 'zip'],
        'keywords': ['nonlocal']
        };
    var stringPrefixes;

    if (!!conf.mode.version && parseInt(conf.mode.version, 10) === 3) {
        commonkeywords = commonkeywords.concat(ka3.keywords);
        commontypes = commontypes.concat(ka3.types);
        stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i");
    } else {
        commonkeywords = commonkeywords.concat(ka2.keywords);
        commontypes = commontypes.concat(ka2.types);
        stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
    }
    var keywords = wordRegexp(commonkeywords);
    var types = wordRegexp(commontypes);
    var blockKeywords = new RegExp("^(" + commonBlockKeywords.join("|") + ")$");

    // tokenizers
    function tokenBase(stream, state) {
        // Handle scope changes
        if (stream.sol()) {
            var scopeOffset = state.scopes[0].offset;
            if (stream.eatSpace()) {
                var lineOffset = stream.indentation();
                if (lineOffset > scopeOffset) {
                    return 'ka-indent';
                } else if (lineOffset < scopeOffset) {
                    return 'ka-dedent';
                }
                return 'whitespace';
            } else {
                if (scopeOffset > 0) {
                    dedent(stream, state);
                }
            }
        }
        if (stream.eatSpace()) {
            return 'ka-space';
        }

        var ch = stream.peek();

        // Handle Comments
        if (ch === '#') {
            stream.skipToEnd();
            return 'ka-comment';
        }

        // Handle Number Literals
        if (stream.match(/^[0-9\.]/, false)) {
            var floatLiteral = false;
            // Floats
            if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) {
                floatLiteral = true;
            }
            if (stream.match(/^\d+\.\d*/)) {
                floatLiteral = true;
            }
            if (stream.match(/^\.\d+/)) {
                floatLiteral = true;
            }
            if (floatLiteral) {
                // Float literals may be "imaginary"
                stream.eat(/J/i);
                return 'ka-literal';
            }
            // Integers
            var intLiteral = false;
            // Hex
            if (stream.match(/^0x[0-9a-f]+/i)) {
                intLiteral = true;
            }
            // Binary
            if (stream.match(/^0b[01]+/i)) {
                intLiteral = true;
            }
            // Octal
            if (stream.match(/^0o[0-7]+/i)) {
                intLiteral = true;
            }
            // Decimal
            if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
                // Decimal literals may be "imaginary"
                stream.eat(/J/i);
                // TODO - Can you have imaginary longs?
                intLiteral = true;
            }
            // Zero by itself with no other piece of number.
            if (stream.match(/^0(?![\dx])/i)) {
                intLiteral = true;
            }
            if (intLiteral) {
                // Integer literals may be "long"
                stream.eat(/L/i);
                return 'ka-literal';
            }
        }

        // Handle Strings
        if (stream.match(stringPrefixes)) {
            state.tokenize = tokenStringFactory(stream.current());
            return state.tokenize(stream, state);
        }

        // Handle operators and Delimiters
        if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) {
            return 'ka-delimiter';
        }
        if (stream.match(doubleOperators)
            || stream.match(singleOperators)
            || stream.match(wordOperators)) {
            return 'ka-operator';
        }
        if (stream.match(singleDelimiters)) {
            return 'ka-delimiter';
        }

        if (stream.match(types)) {
            return 'ka-type';
        }

        if (stream.match(keywords)) {
            return 'ka-keyword';
        }

        if (stream.match(identifiers)) {
            return 'ka-identifier';
        }

        // Handle non-detected items
        stream.next();
        return ERRORCLASS;
    }

    function tokenStringFactory(delimiter) {
        while ('rub'.indexOf(delimiter[0].toLowerCase()) >= 0) {
            delimiter = delimiter.substr(1);
        }
        var delim_re = new RegExp(delimiter);
        var singleline = delimiter.length == 1;
        var OUTCLASS = 'ka-string';

        return function tokenString(stream, state) {
            while (!stream.eol()) {
                stream.eatWhile(/[^'"\\]/);
                if (stream.eat('\\')) {
                    stream.next();
                    if (singleline && stream.eol()) {
                        return OUTCLASS;
                    }
                } else if (stream.match(delim_re)) {
                    state.tokenize = tokenBase;
                    return OUTCLASS;
                } else {
                    stream.eat(/['"]/);
                }
            }
            if (singleline) {
                if (conf.mode.singleLineStringErrors) {
                    OUTCLASS = ERRORCLASS
                } else {
                    state.tokenize = tokenBase;
                }
            }
            return OUTCLASS;
        };
    }

    function indent(stream, state, type) {
        type = type || 'py';
        var indentUnit = 0;
        if (type === 'py') {
            for (var i = 0; i < state.scopes.length; ++i) {
                if (state.scopes[i].type === 'py') {
                    indentUnit = state.scopes[i].offset + conf.indentUnit;
                    break;
                }
            }
        } else {
            indentUnit = stream.column() + stream.current().length;
        }
        state.scopes.unshift({
            offset: indentUnit,
            type: type
        });
    }

    function dedent(stream, state) {
        if (state.scopes.length == 1) return;
        if (state.scopes[0].type === 'py') {
            var _indent = stream.indentation();
            var _indent_index = -1;
            for (var i = 0; i < state.scopes.length; ++i) {
                if (_indent === state.scopes[i].offset) {
                    _indent_index = i;
                    break;
                }
            }
            if (_indent_index === -1) {
                return true;
            }
            while (state.scopes[0].offset !== _indent) {
                state.scopes.shift();
            }
            return false
        } else {
            state.scopes.shift();
            return false;
        }
    }

    function tokenLexer(stream, state) {
        var style = state.tokenize(stream, state);
        var current = stream.current();

        // Handle '.' connected identifiers
        if (current === '.') {
            style = state.tokenize(stream, state);
            current = stream.current();
            if (style === 'ka-identifier') {
                return 'ka-identifier';
            } else {
                return ERRORCLASS;
            }
        }

        // Handle decorators
        if (current === '@') {
            style = state.tokenize(stream, state);
            current = stream.current();
            if (style === 'ka-identifier'
                || current === '@staticmethod'
                || current === '@classmethod') {
                return 'ka-decorator';
            } else {
                return ERRORCLASS;
            }
        }

        // Handle scope changes.
        if (current === 'pass' || current === 'return') {
            state.dedent += 1;
        }
        if ((stream.string.slice(stream.indentation(), stream.pos).match(blockKeywords)
            && !state.lambda && state.scopes[0].type == 'py')
            || style === 'ka-indent') {
            indent(stream, state);
        }
        var delimiter_index = '[({'.indexOf(current);
        if (delimiter_index !== -1) {
            indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1));
        }
        if (style === 'ka-dedent') {
            if (dedent(stream, state)) {
                return ERRORCLASS;
            }
        }
        delimiter_index = '])}'.indexOf(current);
        if (delimiter_index !== -1) {
            if (dedent(stream, state)) {
                return ERRORCLASS;
            }
        }
        if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') {
            if (state.scopes.length > 1) state.scopes.shift();
            state.dedent -= 1;
        }

        return style;
    }

    var external = {
        startState: function(basecolumn) {
            return {
                tokenize: tokenBase,
                scopes: [{
                    offset:basecolumn || 0,
                    type:'py'
                }],
                lastToken: null,
                lambda: false,
                dedent: 0
            };
        },

        token: function(stream, state) {
            var style = tokenLexer(stream, state);

            state.lastToken = {
                style:style,
                content: stream.current()
                };

            if (stream.eol() && stream.lambda) {
                state.lambda = false;
            }

            return style;
        },

        indent: function(state, textAfter) {
            if (state.tokenize != tokenBase) {
                return 0;
            }

            return state.scopes[0].offset;
        }

    };
    return external;
});

CodeMirror.defineMIME("text/x-karel", "karel-en");
<|endoftext|>


<commit_before>(function(exports) {
  'use strict';

  if (exports.EventsManager) {
    return;
  }

  var EventInstance = function(name, namespace) {
    var _isStoppedPropagation = false;

    this.name = name;
    this.namespace = namespace;

    this.stopPropagation = function() {
      _isStoppedPropagation = true;
    };

    this.isStoppedPropagation = function() {
      return _isStoppedPropagation;
    };

    this.resetStopPropagation = function() {
      _isStoppedPropagation = false;
    };
  };

  var EventPriority = function() {
    var _priorities = {
      low: -1,
      normal: 0,
      high: 9999999
    };

    this.formatPriority = function(priority) {
      if (typeof priority == 'undefined' || priority === null) {
        priority = 'normal';
      }

      if (typeof priority == 'string') {
        if (!(priority in _priorities)) {
          priority = 'normal';
        }
        priority = _priorities[priority];
      }

      return priority;
    };

    this.sortByPriority = function(list) {
      list.sort(function(a, b) {
        return b.priority - a.priority;
      });
    };

  };

  exports.EventsManager = function() {

    var _this = this,
        _events = [],
        _eventsAnything = [],
        _priority = new EventPriority(),
        _locks = {};

    function _formatEventNames(eventName) {
      var events = [];

      eventName.split(' ').map(function(name) {
        if (name) {
          events.push(name);
        }
      });

      return events;
    }

    function _namespace(eventName) {
      var namespace = null;
      eventName = eventName.split('.');
      if (eventName.length > 1) {
        namespace = eventName[0];
        eventName = eventName[1];
      }
      else {
        eventName = eventName[0];
      }

      return {
        name: eventName,
        namespace: namespace
      };
    }

    function _checkLock(eventName) {
      if (_locks[eventName]) {
        console.warn('EventsManager: Be careful, event "' + eventName + '" is locked for use');
      }
    }

    this.eventsName = function() {
      var names = [],
          name = null;

      for (name in _events) {
        names.push(name);
      }

      return names;
    };

    this.eventsStack = function(eventName) {
      return (_events[eventName] || []).concat(_eventsAnything);
    };

    function _fireLoop(eventName, eventArgs, events, callback, i, results) {
      i = i || 0;
      results = results || [];

      if (i === events.length) {
        if (callback) {
          callback(results);
        }

        return;
      }

      var evt = events[i];

      if (evt) {
        var eventInstance = new EventInstance(eventName, evt.namespace);

        if (evt.async) {
          evt.func.call(eventInstance, eventArgs, function(result) {
            results.push(typeof result == 'undefined' ? null : result);

            _fireLoop(eventName, eventArgs, events, callback,
              eventInstance.isStoppedPropagation() ? events.length : i + 1,
              results
            );
          });

          return;
        }

        var result = evt.func.call(eventInstance, eventArgs);

        results.push(typeof result == 'undefined' ? null : result);

        _fireLoop(eventName, eventArgs, events, callback,
          eventInstance.isStoppedPropagation() ? events.length : i + 1,
          results
        );

        return;
      }

      _fireLoop(eventName, eventArgs, events, callback, i + 1, results);
    }

    this.fire = function(eventName, eventArgs, callback) {
      var values = [];

      if (typeof _events[eventName] != 'undefined') {

        _checkLock(eventName);

        _locks[eventName] = true;

        _fireLoop(eventName, eventArgs, _events[eventName], function(results) {
          if (callback) {
            callback(results);
          }
        });

        delete _locks[eventName];
      }

      if (_eventsAnything.length) {
        _fireLoop(eventName, eventArgs, _eventsAnything, function(results) {
          if (callback) {
            callback(results);
          }
        });
      }

      return values;
    };

    function _on(eventName, eventFunc, priority, async) {
      priority = _priority.formatPriority(priority);

      _checkLock(eventName);

      _formatEventNames(eventName).map(function(name) {
        name = _namespace(name);

        _events[name.name] = _events[name.name] || [];
        _events[name.name].push({
          namespace: name.namespace,
          priority: priority,
          func: eventFunc,
          async: async || false
        });

        _priority.sortByPriority(_events[name.name]);
      });
    }

    this.on = function(eventName, eventFunc, priority) {
      _on(eventName, eventFunc, priority);

      return _this;
    };

    this.onSafe = function(eventName, eventFunc, priority) {
      _this.off(eventName, eventFunc);

      return _this.on(eventName, eventFunc, priority);
    };

    this.onAsync = function(eventName, eventFunc, priority) {
      _on(eventName, eventFunc, priority, true);

      return _this;
    };

    this.onAsyncSafe = function(eventName, eventFunc, priority) {
      _this.off(eventName, eventFunc);

      return _this.onAsync(eventName, eventFunc, priority);
    };

    this.onAnything = function(eventNamespace, eventFunc, priority) {
      priority = _priority.formatPriority(priority);

      _eventsAnything.push({
        namespace: eventNamespace,
        priority: priority,
        func: eventFunc
      });

      return _this;
    };

    this.onAnythingSafe = function(eventNamespace, eventFunc, priority) {
      _this.offAnything(eventNamespace, eventFunc);

      return _this.onAnything(eventNamespace, eventFunc, priority);
    };

    this.off = function(eventName, eventFunc) {
      _checkLock(eventName);

      _formatEventNames(eventName).map(function(name) {
        name = _namespace(name);

        if (typeof _events[name.name] != 'undefined') {
          for (var i = 0, len = _events[name.name].length; i < len; i++) {
            if (name.namespace && _events[name.name][i].namespace != name.namespace) {
              continue;
            }

            if (_events[name.name][i].func.toString() == eventFunc.toString()) {
              _events[name.name].splice(i, 1);
              break;
            }
          }
        }
      });

      return _this;
    };

    function _offAnything(eventNamespace, eventFunc, entireNamespace) {
      for (var i = 0, len = _eventsAnything.length; i < len; i++) {
        if (_eventsAnything[i].namespace != eventNamespace) {
          continue;
        }

        if (entireNamespace || _eventsAnything[i].func.toString() == eventFunc.toString()) {
          _eventsAnything.splice(i, 1);

          break;
        }
      }
    }

    this.offAnything = function(eventNamespace, eventFunc) {
      _offAnything(eventNamespace, eventFunc);

      return _this;
    };

    this.offNamespace = function(namespace) {
      var name;

      for (name in _events) {
        var event = _events[name],
            newEvent = [];

        for (var i = event.length - 1; i >= 0; i--) {
          if (event[i].namespace !== namespace) {
            newEvent.push(event[i]);
          }
        }

        if (newEvent.length !== event.length) {
          _checkLock(name);
          _events[name] = newEvent;
        }
      }

      _offAnything(namespace, null, true);

      return _this;
    };

    this.offAll = function() {
      _events = [];
      _eventsAnything = [];
    };

  };

})(typeof exports == 'undefined' ? this : exports);
<commit_msg>fix(events-manager): prevent removed events in fire loop<commit_after>(function(exports) {
  'use strict';

  if (exports.EventsManager) {
    return;
  }

  var EventInstance = function(name, namespace) {
    var _isStoppedPropagation = false;

    this.name = name;
    this.namespace = namespace;

    this.stopPropagation = function() {
      _isStoppedPropagation = true;
    };

    this.isStoppedPropagation = function() {
      return _isStoppedPropagation;
    };

    this.resetStopPropagation = function() {
      _isStoppedPropagation = false;
    };
  };

  var EventPriority = function() {
    var _priorities = {
      low: -1,
      normal: 0,
      high: 9999999
    };

    this.formatPriority = function(priority) {
      if (typeof priority == 'undefined' || priority === null) {
        priority = 'normal';
      }

      if (typeof priority == 'string') {
        if (!(priority in _priorities)) {
          priority = 'normal';
        }
        priority = _priorities[priority];
      }

      return priority;
    };

    this.sortByPriority = function(list) {
      list.sort(function(a, b) {
        return b.priority - a.priority;
      });
    };

  };

  exports.EventsManager = function() {

    var _this = this,
        _events = [],
        _eventsAnything = [],
        _priority = new EventPriority(),
        _locks = {};

    function _formatEventNames(eventName) {
      var events = [];

      eventName.split(' ').map(function(name) {
        if (name) {
          events.push(name);
        }
      });

      return events;
    }

    function _namespace(eventName) {
      var namespace = null;
      eventName = eventName.split('.');
      if (eventName.length > 1) {
        namespace = eventName[0];
        eventName = eventName[1];
      }
      else {
        eventName = eventName[0];
      }

      return {
        name: eventName,
        namespace: namespace
      };
    }

    function _checkLock(eventName) {
      if (_locks[eventName]) {
        console.warn('EventsManager: Be careful, event "' + eventName + '" is locked for use');
      }
    }

    this.eventsName = function() {
      var names = [],
          name = null;

      for (name in _events) {
        names.push(name);
      }

      return names;
    };

    this.eventsStack = function(eventName) {
      return (_events[eventName] || []).concat(_eventsAnything);
    };

    function _fireLoop(eventName, eventArgs, events, callback, i, results) {
      i = i || 0;
      results = results || [];

      if (i >= events.length) {
        if (callback) {
          callback(results);
        }

        return;
      }

      var evt = events[i];

      if (evt) {
        var eventInstance = new EventInstance(eventName, evt.namespace);

        if (evt.async) {
          evt.func.call(eventInstance, eventArgs, function(result) {
            results.push(typeof result == 'undefined' ? null : result);

            _fireLoop(eventName, eventArgs, events, callback,
              eventInstance.isStoppedPropagation() ? events.length : i + 1,
              results
            );
          });

          return;
        }

        var result = evt.func.call(eventInstance, eventArgs);

        results.push(typeof result == 'undefined' ? null : result);

        _fireLoop(eventName, eventArgs, events, callback,
          eventInstance.isStoppedPropagation() ? events.length : i + 1,
          results
        );

        return;
      }

      _fireLoop(eventName, eventArgs, events, callback, i + 1, results);
    }

    this.fire = function(eventName, eventArgs, callback) {
      var values = [];

      if (typeof _events[eventName] != 'undefined') {

        _checkLock(eventName);

        _locks[eventName] = true;

        _fireLoop(eventName, eventArgs, _events[eventName], function(results) {
          if (callback) {
            callback(results);
          }
        });

        delete _locks[eventName];
      }

      if (_eventsAnything.length) {
        _fireLoop(eventName, eventArgs, _eventsAnything, function(results) {
          if (callback) {
            callback(results);
          }
        });
      }

      return values;
    };

    function _on(eventName, eventFunc, priority, async) {
      priority = _priority.formatPriority(priority);

      _checkLock(eventName);

      _formatEventNames(eventName).map(function(name) {
        name = _namespace(name);

        _events[name.name] = _events[name.name] || [];
        _events[name.name].push({
          namespace: name.namespace,
          priority: priority,
          func: eventFunc,
          async: async || false
        });

        _priority.sortByPriority(_events[name.name]);
      });
    }

    this.on = function(eventName, eventFunc, priority) {
      _on(eventName, eventFunc, priority);

      return _this;
    };

    this.onSafe = function(eventName, eventFunc, priority) {
      _this.off(eventName, eventFunc);

      return _this.on(eventName, eventFunc, priority);
    };

    this.onAsync = function(eventName, eventFunc, priority) {
      _on(eventName, eventFunc, priority, true);

      return _this;
    };

    this.onAsyncSafe = function(eventName, eventFunc, priority) {
      _this.off(eventName, eventFunc);

      return _this.onAsync(eventName, eventFunc, priority);
    };

    this.onAnything = function(eventNamespace, eventFunc, priority) {
      priority = _priority.formatPriority(priority);

      _eventsAnything.push({
        namespace: eventNamespace,
        priority: priority,
        func: eventFunc
      });

      return _this;
    };

    this.onAnythingSafe = function(eventNamespace, eventFunc, priority) {
      _this.offAnything(eventNamespace, eventFunc);

      return _this.onAnything(eventNamespace, eventFunc, priority);
    };

    this.off = function(eventName, eventFunc) {
      _checkLock(eventName);

      _formatEventNames(eventName).map(function(name) {
        name = _namespace(name);

        if (typeof _events[name.name] != 'undefined') {
          for (var i = 0, len = _events[name.name].length; i < len; i++) {
            if (name.namespace && _events[name.name][i].namespace != name.namespace) {
              continue;
            }

            if (_events[name.name][i].func.toString() == eventFunc.toString()) {
              _events[name.name].splice(i, 1);
              break;
            }
          }
        }
      });

      return _this;
    };

    function _offAnything(eventNamespace, eventFunc, entireNamespace) {
      for (var i = 0, len = _eventsAnything.length; i < len; i++) {
        if (_eventsAnything[i].namespace != eventNamespace) {
          continue;
        }

        if (entireNamespace || _eventsAnything[i].func.toString() == eventFunc.toString()) {
          _eventsAnything.splice(i, 1);

          break;
        }
      }
    }

    this.offAnything = function(eventNamespace, eventFunc) {
      _offAnything(eventNamespace, eventFunc);

      return _this;
    };

    this.offNamespace = function(namespace) {
      var name;

      for (name in _events) {
        var event = _events[name],
            newEvent = [];

        for (var i = event.length - 1; i >= 0; i--) {
          if (event[i].namespace !== namespace) {
            newEvent.push(event[i]);
          }
        }

        if (newEvent.length !== event.length) {
          _checkLock(name);
          _events[name] = newEvent;
        }
      }

      _offAnything(namespace, null, true);

      return _this;
    };

    this.offAll = function() {
      _events = [];
      _eventsAnything = [];
    };

  };

})(typeof exports == 'undefined' ? this : exports);
<|endoftext|>