<commit_before>//----------------------------------------------------------------------------
//  Copyright (C) 2013  The IPython Development Team
//
//  Distributed under the terms of the BSD License.  The full license is in
//  the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------

//============================================================================
// WidgetModel, WidgetView, and WidgetManager
//============================================================================
/**
 * Base Widget classes
 * @module IPython
 * @namespace IPython
 * @submodule widget
 */

"use strict";

// Use require.js 'define' method so that require.js is intelligent enough to
// syncronously load everything within this file when it is being 'required'
// elsewhere.
define(["components/underscore/underscore-min",
         "components/backbone/backbone-min",
        ], function(underscore, backbone){

    //--------------------------------------------------------------------
    // WidgetModel class
    //--------------------------------------------------------------------
    var WidgetModel = Backbone.Model.extend({
        constructor: function(comm_manager, comm, widget_manager) {
            this.comm_manager = comm_manager;
            this.widget_manager = widget_manager;
            this.pending_msgs = 0;
            this.msg_throttle = 3;
            this.msg_buffer = null;
            this.views = {};

            // Remember comm associated with the model.
            this.comm = comm;
            comm.model = this;

            // Hook comm messages up to model.
            comm.on_close($.proxy(this._handle_comm_closed, this));
            comm.on_msg($.proxy(this._handle_comm_msg, this));

            return Backbone.Model.apply(this);
        },

        update_other_views: function(caller) {
            this.last_modified_view = caller;
            this.save(this.changedAttributes(), {patch: true});

            for (var cell in this.views) {
                var views = this.views[cell];
                for (var view_index in views) {
                    var view = views[view_index];
                    if (view !== caller) {
                        view.update();
                    }
                }
            }
        },

        // Handle when a widget is closed.
        _handle_comm_closed: function (msg) {
            for (var cell in this.views) {
                var views = this.views[cell];
                for (var view_index in views) {
                    var view = views[view_index];
                    view.remove();
                }
            }
        },

        // Handle incomming comm msg.
        _handle_comm_msg: function (msg) {
            var method = msg.content.data.method;
            switch (method){
                case 'display':

                    // Try to get the cell index.
                    var cell = this._get_msg_cell(msg.parent_header.msg_id);
                    if (cell == null) {
                        console.log("Could not determine where the display" +
                            " message was from.  Widget will not be displayed")
                    } else {
                        this._display_view(msg.content.data.view_name,
                        msg.content.data.parent,
                        cell);
                    }
                    break;
                case 'update':
                    this._handle_update(msg.content.data.state);
                    break;
            }
        },

        // Handle when a widget is updated via the python side.
        _handle_update: function (state) {
            this.updating = true;
            try {
                for (var key in state) {
                    if (state.hasOwnProperty(key)) {
                        if (key == "_css"){
                            this.css = state[key];
                        } else {
                            this.set(key, state[key]);
                        }
                    }
                }
                this.id = this.comm.comm_id;
                this.save();
            } finally {
                this.updating = false;
            }
        },

        _handle_status: function (cell, msg) {
            //execution_state : ('busy', 'idle', 'starting')
            if (msg.content.execution_state=='idle') {

                // Send buffer if this message caused another message to be
                // throttled.
                if (this.msg_buffer != null &&
                    this.msg_throttle == this.pending_msgs) {

                    var cell = this._get_msg_cell(msg.parent_header.msg_id);
                    var callbacks = this._make_callbacks(cell);
                    var data = {sync_method: 'update', sync_data: this.msg_buffer};
                    this.comm.send(data, callbacks);
                    this.msg_buffer = null;
                } else {

                    // Only decrease the pending message count if the buffer
                    // doesn't get flushed (sent).
                    --this.pending_msgs;
                }
            }
        },

        // Custom syncronization logic.
        _handle_sync: function (method, options) {
            var model_json = this.toJSON();

            // Only send updated state if the state hasn't been changed
            // during an update.
            if (!this.updating) {
                if (this.pending_msgs >= this.msg_throttle) {
                    // The throttle has been exceeded, buffer the current msg so
                    // it can be sent once the kernel has finished processing
                    // some of the existing messages.
                    if (method=='patch') {
                        if (this.msg_buffer == null) {
                            this.msg_buffer = $.extend({}, model_json); // Copy
                        }
                        for (var attr in options.attrs) {
                            this.msg_buffer[attr] = options.attrs[attr];
                        }
                    } else {
                        this.msg_buffer = $.extend({}, model_json); // Copy
                    }

                } else {
                    // We haven't exceeded the throttle, send the message like
                    // normal.  If this is a patch operation, just send the
                    // changes.
                    var send_json = model_json;
                    if (method=='patch') {
                        send_json = {};
                        for (var attr in options.attrs) {
                            send_json[attr] = options.attrs[attr];
                        }
                    }

                    var data = {sync_method: method, sync_data: send_json};

                    var cell = null;
                    if (this.last_modified_view != undefined && this.last_modified_view != null) {
                        cell = this.last_modified_view.cell;
                    }

                    var callbacks = this._make_callbacks(cell);
                    this.comm.send(data, callbacks);
                    this.pending_msgs++;
                }
            }

            // Since the comm is a one-way communication, assume the message
            // arrived.
            return model_json;
        },

        // Create view that represents the model.
        _display_view: function (view_name, parent_comm_id, cell) {
            var new_views = [];

            var displayed = false;
            if (parent_comm_id != undefined) {
                var parent_comm = this.comm_manager.comms[parent_comm_id];
                var parent_model = parent_comm.model;
                var parent_views = parent_model.views[cell];
                for (var parent_view_index in parent_views) {
                    var parent_view = parent_views[parent_view_index];
                    if (parent_view.display_child != undefined) {
                        var view = this._create_view(view_name, cell);
                        new_views.push(view);
                        parent_view.display_child(view);
                        displayed = true;
                    }
                }
            }

            if (!displayed) {
                // No parent view is defined or exists.  Add the view's
                // element to cell's widget div.
                var view = this._create_view(view_name, cell);
                new_views.push(view);

                if (cell.widget_subarea != undefined && cell.widget_subarea != null) {
                    cell.widget_area.show();
                    cell.widget_subarea.append(view.$el);
                }
            }

            for (var view_index in new_views) {
                var view = new_views[view_index];
                view.update();
            }
        },

        // Create a view
        _create_view: function (view_name, cell) {
            var view = new this.widget_manager.widget_view_types[view_name]({model: this});
            view.render();
            if (this.views[cell]==undefined) {
                this.views[cell] = []
            }
            this.views[cell].push(view);
            view.cell = cell;

            // Handle when the view element is remove from the page.
            var that = this;
            view.$el.on("remove", function(){
                var index = that.views[cell].indexOf(view);
                if (index > -1) {
                    that.views[cell].splice(index, 1);
                }
                view.remove(); // Clean-up view
                if (that.views[cell].length()==0) {
                    delete that.views[cell];
                }

                // Close the comm if there are no views left.
                if (that.views.length()==0) {
                    that.comm.close();
                }
            });
            return view;
        },

        // Build a callback dict.
        _make_callbacks: function (cell) {
            var callbacks = {};
            if (cell != null && cell.output_area != undefined && cell.output_area != null) {
                var that = this;
                callbacks = {
                    iopub : {
                        output : $.proxy(cell.output_area.handle_output, cell.output_area),
                        clear_output : $.proxy(cell.output_area.handle_clear_output, cell.output_area),
                        status : function(msg){
                            that._handle_status(cell, msg);
                        },
                        get_cell : function() {
                            if (that.last_modified_view != undefined &&
                                that.last_modified_view.cell != undefined) {
                                return that.last_modified_view.cell;
                            } else {
                                return null
                            }
                        },
                    },
                };
            }
            return callbacks;
        },

        // Get the output area corresponding to the msg_id.
        // cell is an instance of IPython.Cell
        _get_msg_cell: function (msg_id) {

            // First, check to see if the msg was triggered by cell execution.
            var cell = this.widget_manager.get_msg_cell(msg_id);
            if (cell != null) {
                return cell;
            }

            // Second, check to see if a get_cell callback was defined
            // for the message.  get_cell callbacks are registered for
            // widget messages, so this block is actually checking to see if the
            // message was triggered by a widget.
            var kernel = this.comm_manager.kernel;
            var callbacks = kernel.get_callbacks_for_msg(msg_id);
            if (callbacks != undefined &&
                callbacks.iopub != undefined &&
                callbacks.iopub.get_cell != undefined) {

                return callbacks.iopub.get_cell();
            }

            // Not triggered by a cell or widget (no get_cell callback
            // exists).
            return null;
        },

    });

    //--------------------------------------------------------------------
    // WidgetView class
    //--------------------------------------------------------------------
    var WidgetView = Backbone.View.extend({

        initialize: function() {
            this.visible = true;
            this.model.on('change',this.update,this);
            this._add_class_calls = this.model.get('_add_class')[0];
            this._remove_class_calls = this.model.get('_remove_class')[0];
        },

        update: function() {
            if (this.model.get('visible') != undefined) {
                if (this.visible != this.model.get('visible')) {
                    this.visible = this.model.get('visible');
                    if (this.visible) {
                        this.$el.show();
                    } else {
                        this.$el.hide();
                    }
                }
            }

            if (this.model.css != undefined) {
                for (var selector in this.model.css) {
                    if (this.model.css.hasOwnProperty(selector)) {

                        // Apply the css traits to all elements that match the selector.
                        var elements = this._get_selector_element(selector);
                        if (elements.length > 0) {
                            var css_traits = this.model.css[selector];
                            for (var css_key in css_traits) {
                                if (css_traits.hasOwnProperty(css_key)) {
                                    elements.css(css_key, css_traits[css_key]);
                                }
                            }
                        }
                    }
                }
            }

            var add_class = this.model.get('_add_class');
            if (add_class != undefined){
                var add_class_calls = add_class[0];
                if (add_class_calls > this._add_class_calls) {
                    this._add_class_calls = add_class_calls;
                    var elements = this._get_selector_element(add_class[1]);
                    if (elements.length > 0) {
                        elements.addClass(add_class[2]);
                    }
                }
            }

            var remove_class = this.model.get('_remove_class');
            if (remove_class != undefined){
                var remove_class_calls = remove_class[0];
                if (remove_class_calls > this._remove_class_calls) {
                    this._remove_class_calls = remove_class_calls;
                    var elements = this._get_selector_element(remove_class[1]);
                    if (elements.length > 0) {
                        elements.removeClass(remove_class[2]);
                    }
                }
            }
        },

        _get_selector_element: function(selector) {
            // Get the elements via the css selector.  If the selector is
            // blank, apply the style to the $el_to_style element.  If
            // the $el_to_style element is not defined, use apply the
            // style to the view's element.
            var elements = this.$el.find(selector);
            if (selector=='') {
                if (this.$el_to_style == undefined) {
                    elements = this.$el;
                } else {
                    elements = this.$el_to_style;
                }
            }
            return elements;
        },
    });

    //--------------------------------------------------------------------
    // WidgetManager class
    //--------------------------------------------------------------------
    var WidgetManager = function(){
        this.comm_manager = null;
        this.widget_model_types = {};
        this.widget_view_types = {};

        var that = this;
        Backbone.sync = function(method, model, options, error) {
            var result = model._handle_sync(method, options);
            if (options.success) {
              options.success(result);
            }
        };
    }

    WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
        this.comm_manager = comm_manager;

        // Register already register widget model types with the comm manager.
        for (var widget_model_name in this.widget_model_types) {
            this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
        }
    }

    WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
        // Register the widget with the comm manager.  Make sure to pass this object's context
        // in so `this` works in the call back.
        if (this.comm_manager!=null) {
            this.comm_manager.register_target(widget_model_name, $.proxy(this.handle_com_open, this));
        }
        this.widget_model_types[widget_model_name] = widget_model_type;
    }

    WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
        this.widget_view_types[widget_view_name] = widget_view_type;
    }

    WidgetManager.prototype.handle_com_open = function (comm, msg) {
        var widget_type_name = msg.content.target_name;
        var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this);
    }

    WidgetManager.prototype.get_msg_cell = function (msg_id) {
        if (IPython.notebook != undefined && IPython.notebook != null) {
            return IPython.notebook.get_msg_cell(msg_id);
        }
    }

    //--------------------------------------------------------------------
    // Init code
    //--------------------------------------------------------------------
    IPython.WidgetManager = WidgetManager;
    IPython.WidgetModel = WidgetModel;
    IPython.WidgetView = WidgetView;

    if (IPython.widget_manager==undefined || IPython.widget_manager==null) {
        IPython.widget_manager = new WidgetManager();
    }

    return IPython.widget_manager;
});
<commit_msg>Privatize _handle_com_open of WidgetManager<commit_after>//----------------------------------------------------------------------------
//  Copyright (C) 2013  The IPython Development Team
//
//  Distributed under the terms of the BSD License.  The full license is in
//  the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------

//============================================================================
// WidgetModel, WidgetView, and WidgetManager
//============================================================================
/**
 * Base Widget classes
 * @module IPython
 * @namespace IPython
 * @submodule widget
 */

"use strict";

// Use require.js 'define' method so that require.js is intelligent enough to
// syncronously load everything within this file when it is being 'required'
// elsewhere.
define(["components/underscore/underscore-min",
         "components/backbone/backbone-min",
        ], function(underscore, backbone){

    //--------------------------------------------------------------------
    // WidgetModel class
    //--------------------------------------------------------------------
    var WidgetModel = Backbone.Model.extend({
        constructor: function(comm_manager, comm, widget_manager) {
            this.comm_manager = comm_manager;
            this.widget_manager = widget_manager;
            this.pending_msgs = 0;
            this.msg_throttle = 3;
            this.msg_buffer = null;
            this.views = {};

            // Remember comm associated with the model.
            this.comm = comm;
            comm.model = this;

            // Hook comm messages up to model.
            comm.on_close($.proxy(this._handle_comm_closed, this));
            comm.on_msg($.proxy(this._handle_comm_msg, this));

            return Backbone.Model.apply(this);
        },

        update_other_views: function(caller) {
            this.last_modified_view = caller;
            this.save(this.changedAttributes(), {patch: true});

            for (var cell in this.views) {
                var views = this.views[cell];
                for (var view_index in views) {
                    var view = views[view_index];
                    if (view !== caller) {
                        view.update();
                    }
                }
            }
        },

        // Handle when a widget is closed.
        _handle_comm_closed: function (msg) {
            for (var cell in this.views) {
                var views = this.views[cell];
                for (var view_index in views) {
                    var view = views[view_index];
                    view.remove();
                }
            }
        },

        // Handle incomming comm msg.
        _handle_comm_msg: function (msg) {
            var method = msg.content.data.method;
            switch (method){
                case 'display':

                    // Try to get the cell index.
                    var cell = this._get_msg_cell(msg.parent_header.msg_id);
                    if (cell == null) {
                        console.log("Could not determine where the display" +
                            " message was from.  Widget will not be displayed")
                    } else {
                        this._display_view(msg.content.data.view_name,
                        msg.content.data.parent,
                        cell);
                    }
                    break;
                case 'update':
                    this._handle_update(msg.content.data.state);
                    break;
            }
        },

        // Handle when a widget is updated via the python side.
        _handle_update: function (state) {
            this.updating = true;
            try {
                for (var key in state) {
                    if (state.hasOwnProperty(key)) {
                        if (key == "_css"){
                            this.css = state[key];
                        } else {
                            this.set(key, state[key]);
                        }
                    }
                }
                this.id = this.comm.comm_id;
                this.save();
            } finally {
                this.updating = false;
            }
        },

        _handle_status: function (cell, msg) {
            //execution_state : ('busy', 'idle', 'starting')
            if (msg.content.execution_state=='idle') {

                // Send buffer if this message caused another message to be
                // throttled.
                if (this.msg_buffer != null &&
                    this.msg_throttle == this.pending_msgs) {

                    var cell = this._get_msg_cell(msg.parent_header.msg_id);
                    var callbacks = this._make_callbacks(cell);
                    var data = {sync_method: 'update', sync_data: this.msg_buffer};
                    this.comm.send(data, callbacks);
                    this.msg_buffer = null;
                } else {

                    // Only decrease the pending message count if the buffer
                    // doesn't get flushed (sent).
                    --this.pending_msgs;
                }
            }
        },

        // Custom syncronization logic.
        _handle_sync: function (method, options) {
            var model_json = this.toJSON();

            // Only send updated state if the state hasn't been changed
            // during an update.
            if (!this.updating) {
                if (this.pending_msgs >= this.msg_throttle) {
                    // The throttle has been exceeded, buffer the current msg so
                    // it can be sent once the kernel has finished processing
                    // some of the existing messages.
                    if (method=='patch') {
                        if (this.msg_buffer == null) {
                            this.msg_buffer = $.extend({}, model_json); // Copy
                        }
                        for (var attr in options.attrs) {
                            this.msg_buffer[attr] = options.attrs[attr];
                        }
                    } else {
                        this.msg_buffer = $.extend({}, model_json); // Copy
                    }

                } else {
                    // We haven't exceeded the throttle, send the message like
                    // normal.  If this is a patch operation, just send the
                    // changes.
                    var send_json = model_json;
                    if (method=='patch') {
                        send_json = {};
                        for (var attr in options.attrs) {
                            send_json[attr] = options.attrs[attr];
                        }
                    }

                    var data = {sync_method: method, sync_data: send_json};

                    var cell = null;
                    if (this.last_modified_view != undefined && this.last_modified_view != null) {
                        cell = this.last_modified_view.cell;
                    }

                    var callbacks = this._make_callbacks(cell);
                    this.comm.send(data, callbacks);
                    this.pending_msgs++;
                }
            }

            // Since the comm is a one-way communication, assume the message
            // arrived.
            return model_json;
        },

        // Create view that represents the model.
        _display_view: function (view_name, parent_comm_id, cell) {
            var new_views = [];

            var displayed = false;
            if (parent_comm_id != undefined) {
                var parent_comm = this.comm_manager.comms[parent_comm_id];
                var parent_model = parent_comm.model;
                var parent_views = parent_model.views[cell];
                for (var parent_view_index in parent_views) {
                    var parent_view = parent_views[parent_view_index];
                    if (parent_view.display_child != undefined) {
                        var view = this._create_view(view_name, cell);
                        new_views.push(view);
                        parent_view.display_child(view);
                        displayed = true;
                    }
                }
            }

            if (!displayed) {
                // No parent view is defined or exists.  Add the view's
                // element to cell's widget div.
                var view = this._create_view(view_name, cell);
                new_views.push(view);

                if (cell.widget_subarea != undefined && cell.widget_subarea != null) {
                    cell.widget_area.show();
                    cell.widget_subarea.append(view.$el);
                }
            }

            for (var view_index in new_views) {
                var view = new_views[view_index];
                view.update();
            }
        },

        // Create a view
        _create_view: function (view_name, cell) {
            var view = new this.widget_manager.widget_view_types[view_name]({model: this});
            view.render();
            if (this.views[cell]==undefined) {
                this.views[cell] = []
            }
            this.views[cell].push(view);
            view.cell = cell;

            // Handle when the view element is remove from the page.
            var that = this;
            view.$el.on("remove", function(){
                var index = that.views[cell].indexOf(view);
                if (index > -1) {
                    that.views[cell].splice(index, 1);
                }
                view.remove(); // Clean-up view
                if (that.views[cell].length()==0) {
                    delete that.views[cell];
                }

                // Close the comm if there are no views left.
                if (that.views.length()==0) {
                    that.comm.close();
                }
            });
            return view;
        },

        // Build a callback dict.
        _make_callbacks: function (cell) {
            var callbacks = {};
            if (cell != null && cell.output_area != undefined && cell.output_area != null) {
                var that = this;
                callbacks = {
                    iopub : {
                        output : $.proxy(cell.output_area.handle_output, cell.output_area),
                        clear_output : $.proxy(cell.output_area.handle_clear_output, cell.output_area),
                        status : function(msg){
                            that._handle_status(cell, msg);
                        },
                        get_cell : function() {
                            if (that.last_modified_view != undefined &&
                                that.last_modified_view.cell != undefined) {
                                return that.last_modified_view.cell;
                            } else {
                                return null
                            }
                        },
                    },
                };
            }
            return callbacks;
        },

        // Get the output area corresponding to the msg_id.
        // cell is an instance of IPython.Cell
        _get_msg_cell: function (msg_id) {

            // First, check to see if the msg was triggered by cell execution.
            var cell = this.widget_manager.get_msg_cell(msg_id);
            if (cell != null) {
                return cell;
            }

            // Second, check to see if a get_cell callback was defined
            // for the message.  get_cell callbacks are registered for
            // widget messages, so this block is actually checking to see if the
            // message was triggered by a widget.
            var kernel = this.comm_manager.kernel;
            var callbacks = kernel.get_callbacks_for_msg(msg_id);
            if (callbacks != undefined &&
                callbacks.iopub != undefined &&
                callbacks.iopub.get_cell != undefined) {

                return callbacks.iopub.get_cell();
            }

            // Not triggered by a cell or widget (no get_cell callback
            // exists).
            return null;
        },

    });

    //--------------------------------------------------------------------
    // WidgetView class
    //--------------------------------------------------------------------
    var WidgetView = Backbone.View.extend({

        initialize: function() {
            this.visible = true;
            this.model.on('change',this.update,this);
            this._add_class_calls = this.model.get('_add_class')[0];
            this._remove_class_calls = this.model.get('_remove_class')[0];
        },

        update: function() {
            if (this.model.get('visible') != undefined) {
                if (this.visible != this.model.get('visible')) {
                    this.visible = this.model.get('visible');
                    if (this.visible) {
                        this.$el.show();
                    } else {
                        this.$el.hide();
                    }
                }
            }

            if (this.model.css != undefined) {
                for (var selector in this.model.css) {
                    if (this.model.css.hasOwnProperty(selector)) {

                        // Apply the css traits to all elements that match the selector.
                        var elements = this._get_selector_element(selector);
                        if (elements.length > 0) {
                            var css_traits = this.model.css[selector];
                            for (var css_key in css_traits) {
                                if (css_traits.hasOwnProperty(css_key)) {
                                    elements.css(css_key, css_traits[css_key]);
                                }
                            }
                        }
                    }
                }
            }

            var add_class = this.model.get('_add_class');
            if (add_class != undefined){
                var add_class_calls = add_class[0];
                if (add_class_calls > this._add_class_calls) {
                    this._add_class_calls = add_class_calls;
                    var elements = this._get_selector_element(add_class[1]);
                    if (elements.length > 0) {
                        elements.addClass(add_class[2]);
                    }
                }
            }

            var remove_class = this.model.get('_remove_class');
            if (remove_class != undefined){
                var remove_class_calls = remove_class[0];
                if (remove_class_calls > this._remove_class_calls) {
                    this._remove_class_calls = remove_class_calls;
                    var elements = this._get_selector_element(remove_class[1]);
                    if (elements.length > 0) {
                        elements.removeClass(remove_class[2]);
                    }
                }
            }
        },

        _get_selector_element: function(selector) {
            // Get the elements via the css selector.  If the selector is
            // blank, apply the style to the $el_to_style element.  If
            // the $el_to_style element is not defined, use apply the
            // style to the view's element.
            var elements = this.$el.find(selector);
            if (selector=='') {
                if (this.$el_to_style == undefined) {
                    elements = this.$el;
                } else {
                    elements = this.$el_to_style;
                }
            }
            return elements;
        },
    });

    //--------------------------------------------------------------------
    // WidgetManager class
    //--------------------------------------------------------------------
    var WidgetManager = function(){
        this.comm_manager = null;
        this.widget_model_types = {};
        this.widget_view_types = {};

        var that = this;
        Backbone.sync = function(method, model, options, error) {
            var result = model._handle_sync(method, options);
            if (options.success) {
              options.success(result);
            }
        };
    }

    WidgetManager.prototype.attach_comm_manager = function (comm_manager) {
        this.comm_manager = comm_manager;

        // Register already register widget model types with the comm manager.
        for (var widget_model_name in this.widget_model_types) {
            this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
        }
    }

    WidgetManager.prototype.register_widget_model = function (widget_model_name, widget_model_type) {
        // Register the widget with the comm manager.  Make sure to pass this object's context
        // in so `this` works in the call back.
        if (this.comm_manager!=null) {
            this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this));
        }
        this.widget_model_types[widget_model_name] = widget_model_type;
    }

    WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) {
        this.widget_view_types[widget_view_name] = widget_view_type;
    }

    WidgetManager.prototype.get_msg_cell = function (msg_id) {
        if (IPython.notebook != undefined && IPython.notebook != null) {
            return IPython.notebook.get_msg_cell(msg_id);
        }
    }

    WidgetManager.prototype._handle_com_open = function (comm, msg) {
        var widget_type_name = msg.content.target_name;
        var widget_model = new this.widget_model_types[widget_type_name](this.comm_manager, comm, this);
    }

    //--------------------------------------------------------------------
    // Init code
    //--------------------------------------------------------------------
    IPython.WidgetManager = WidgetManager;
    IPython.WidgetModel = WidgetModel;
    IPython.WidgetView = WidgetView;

    if (IPython.widget_manager==undefined || IPython.widget_manager==null) {
        IPython.widget_manager = new WidgetManager();
    }

    return IPython.widget_manager;
});
<|endoftext|>


<commit_before>module.exports = function(config) {
    config.set({

        // base path, that will be used to resolve files and exclude
        basePath: '',

        frameworks: ['jasmine'],

        plugins: [
            'karma-jasmine',
            'karma-chrome-launcher'
        ],

        // list of files / patterns to load in the browser
        files: [
            'bower_components/angular/angular.js',
            'bower_components/angular-mocks/angular-mocks.js',
            'dist/angular-socialsharing.js',
            'test/unit/**/*.js'
        ],

        // list of files to exclude
        exclude: [],

        // test results reporter to use
        // possible values: 'dots', 'progress', 'junit'
        reporters: ['progress'],

        // web server port
        port: 9876,

        // cli runner port
        runnerPort: 9100,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging
        // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
        logLevel: config.LOG_INFO,

        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,

        // Start these browsers, currently available:
        // - Chrome
        // - ChromeCanary
        // - Firefox
        // - Opera
        // - Safari (only Mac)
        // - PhantomJS
        // - IE (only Windows)
        browsers: ['Chrome'],
        customLaunchers: {
          Chrome_travis_ci: {
            base: 'Chrome',
            flags: ['--no-sandbox']
          }
        },

        // If browser does not capture in given timeout [ms], kill it
        captureTimeout: 60000,

        // Continuous Integration mode
        // if true, it capture browsers, run tests and exit
        singleRun: false

    });
};

<commit_msg>change log level to see output in travis ci<commit_after>module.exports = function(config) {
    config.set({

        // base path, that will be used to resolve files and exclude
        basePath: '',

        frameworks: ['jasmine'],

        plugins: [
            'karma-jasmine',
            'karma-chrome-launcher'
        ],

        // list of files / patterns to load in the browser
        files: [
            'bower_components/angular/angular.js',
            'bower_components/angular-mocks/angular-mocks.js',
            'dist/angular-socialsharing.js',
            'test/unit/**/*.js'
        ],

        // list of files to exclude
        exclude: [],

        // test results reporter to use
        // possible values: 'dots', 'progress', 'junit'
        reporters: ['progress'],

        // web server port
        port: 9876,

        // cli runner port
        runnerPort: 9100,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging
        // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
        logLevel: config.LOG_DEBUG,

        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,

        // Start these browsers, currently available:
        // - Chrome
        // - ChromeCanary
        // - Firefox
        // - Opera
        // - Safari (only Mac)
        // - PhantomJS
        // - IE (only Windows)
        browsers: ['Chrome'],
        customLaunchers: {
          Chrome_travis_ci: {
            base: 'Chrome',
            flags: ['--no-sandbox']
          }
        },

        // If browser does not capture in given timeout [ms], kill it
        captureTimeout: 60000,

        // Continuous Integration mode
        // if true, it capture browsers, run tests and exit
        singleRun: false

    });
};

<|endoftext|>


<commit_before>module.exports = function(config) {
  var customLaunchers = {
    sl_chrome: {
      base: 'SauceLabs',
      browserName: 'chrome',
      platform: 'Windows 7',
      version: '35'
    },
    sl_firefox: {
      base: 'SauceLabs',
      browserName: 'firefox',
      version: '30'
    },
    sl_ie_11: {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      platform: 'Windows 8',
      version: '10'
    },
    sl_ie_9: {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      platform: 'Windows 7',
      version: '9'
    }
  };
  config.set({
    basePath: '',
    frameworks: ['mocha', 'chai-jquery', 'jquery-1.11.0', 'sinon-chai', 'chai', 'fixture'],
    files: [
      'public/javascripts/vendor/xhr.min.js',
      'public/javascripts/vendor/time.min.js',
      'public/javascripts/vendor/timeFormat.min.js',
      'public/javascripts/vendor/handlebars-v3.0.3.js',
      'public/javascripts/vendor/raphael-min.js',
      'public/javascripts/vendor/morris.min.js',
      'public/javascripts/helpers/helper.js',
      'public/javascripts/helper/templateHelper.js',
      'public/javascripts/landing-pages.js',
      'public/javascripts/traffic.js',
      'tests/**/*Spec.js',
      'tests/fixtures/**/*'
    ],
    exclude: [
      '**/*.swp'
    ],
    preprocessors: {
      'tests/**/*.json'   : ['json_fixtures']
    },
    jsonFixturesPreprocessor: {
      variableName: '__json__'
    },
    reporters: ['progress', 'saucelabs'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS'],
    singleRun: true,
    sauceLabs: {
      testName: 'Web App Unit Tests',
      tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER,
      username: process.env.SAUCE_USERNAME,
      accessKey: process.env.SAUCE_ACCESS_KEY,
      startConnect: true,
      recordVideo: true,
      recordScreenshots: true
    },
    customLaunchers: customLaunchers,
    browsers: Object.keys(customLaunchers),
    browserDisconnectTimeout : 10000,
    browserDisconnectTolerance : 1,
    browserNoActivityTimeout : 4*60*1000,
    captureTimeout : 4*60*1000
  });
};
<commit_msg>Add search to karma saucelabs file<commit_after>module.exports = function(config) {
  var customLaunchers = {
    sl_chrome: {
      base: 'SauceLabs',
      browserName: 'chrome',
      platform: 'Windows 7',
      version: '35'
    },
    sl_firefox: {
      base: 'SauceLabs',
      browserName: 'firefox',
      version: '30'
    },
    sl_ie_11: {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      platform: 'Windows 8',
      version: '10'
    },
    sl_ie_9: {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      platform: 'Windows 7',
      version: '9'
    }
  };
  config.set({
    basePath: '',
    frameworks: ['mocha', 'chai-jquery', 'jquery-1.11.0', 'sinon-chai', 'chai', 'fixture'],
    files: [
      'public/javascripts/vendor/xhr.min.js',
      'public/javascripts/vendor/time.min.js',
      'public/javascripts/vendor/timeFormat.min.js',
      'public/javascripts/vendor/handlebars-v3.0.3.js',
      'public/javascripts/vendor/raphael-min.js',
      'public/javascripts/vendor/morris.min.js',
      'public/javascripts/helpers/helper.js',
      'public/javascripts/helper/templateHelper.js',
      'public/javascripts/landing-pages.js',
      'public/javascripts/search.js',
      'public/javascripts/traffic.js',
      'tests/**/*Spec.js',
      'tests/fixtures/**/*'
    ],
    exclude: [
      '**/*.swp'
    ],
    preprocessors: {
      'tests/**/*.json'   : ['json_fixtures']
    },
    jsonFixturesPreprocessor: {
      variableName: '__json__'
    },
    reporters: ['progress', 'saucelabs'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS'],
    singleRun: true,
    sauceLabs: {
      testName: 'Web App Unit Tests',
      tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER,
      username: process.env.SAUCE_USERNAME,
      accessKey: process.env.SAUCE_ACCESS_KEY,
      startConnect: true,
      recordVideo: true,
      recordScreenshots: true
    },
    customLaunchers: customLaunchers,
    browsers: Object.keys(customLaunchers),
    browserDisconnectTimeout : 10000,
    browserDisconnectTolerance : 1,
    browserNoActivityTimeout : 4*60*1000,
    captureTimeout : 4*60*1000
  });
};
<|endoftext|>


<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Skywriter.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Joe Walker (jwalker@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

define(function(require, exports, module) {

var editorCss = require("text!cockpit/ui/cliView.css");
var dom = require("pilot/dom");
dom.importCssString(editorCss);

var canon = require("pilot/canon");
var Status = require('pilot/types').Status;
var keyutil = require('pilot/keyboard/keyutil');

var CliRequisition = require('cockpit/cli').CliRequisition;
var Hint = require('cockpit/cli').Hint;
var RequestView = require('cockpit/ui/requestView').RequestView;

var NO_HINT = new Hint(Status.VALID, '', 0, 0);

/**
 * On startup we need to:
 * 1. Add 3 sets of elements to the DOM for:
 * - command line output
 * - input hints
 * - completion
 * 2. Attach a set of events so the command line works
 */
exports.startup = function(data, reason) {
    var cli = new CliRequisition(data.env);
    var cliView = new CliView(cli, data.env);
};

/**
 * A class to handle the simplest UI implementation
 */
function CliView(cli, env) {
    this.cli = cli;
    this.doc = document;
    this.win = this.doc.defaultView;

    this.settings = env.settings;
    this.hintDirection = this.settings.getSetting('hintDirection');
    this.outputDirection = this.settings.getSetting('outputDirection');
    this.outputHeight = this.settings.getSetting('outputHeight');

    // If the requisition tells us something has changed, we use this to know
    // if we should ignore it
    this.isUpdating = false;

    this.createElements();
    this.update();
}
CliView.prototype = {
    /**
     * Create divs for completion, hints and output
     */
    createElements: function() {

        // TODO: we should have a better way to specify command lines???
        this.element = this.doc.getElementById('cockpitInput');
        if (!this.element) {
            console.log('No element with an id of cockpit. Bailing on cli');
            return;
        }
        var input = this.element;

        this.output = this.doc.getElementById('cockpitOutput');
        this.popupOutput = (this.output == null);
        if (!this.output) {
            this.output = this.doc.createElement('div');
            this.output.id = 'cockpitOutput';
            this.output.className = 'cptFocusPopup';
            input.parentNode.insertBefore(this.output, input.nextSibling);

            var setMaxOutputHeight = function() {
                this.output.style.maxHeight = this.outputHeight.get() + 'px';
            }.bind(this);
            this.outputHeight.addEventListener('change', setMaxOutputHeight);
            setMaxOutputHeight();
        }

        this.completer = this.doc.createElement('div');
        this.completer.className = 'cptCompletion VALID';
        var style = window.getComputedStyle(input);
        this.completer.style.color = style.color;
        this.completer.style.fontSize = style.fontSize;
        this.completer.style.fontFamily = style.fontFamily;
        this.completer.style.fontWeight = style.fontWeight;
        this.completer.style.fontStyle = style.fontStyle;
        input.parentNode.insertBefore(this.completer, input.nextSibling);

        // Transfer background styling to the completer.
        this.completer.style.backgroundColor = input.style.backgroundColor;
        input.style.backgroundColor = 'transparent';

        this.hinter = this.doc.createElement('div');
        this.hinter.className = 'cptHints cptFocusPopup';
        input.parentNode.insertBefore(this.hinter, input.nextSibling);

        var resizer = this.resizer.bind(this);
        this.win.addEventListener('resize', resizer, false);
        this.hintDirection.addEventListener('change', resizer);
        this.outputDirection.addEventListener('change', resizer);
        resizer();

        canon.addEventListener('output',  function(ev) {
            new RequestView(ev.request, this);
        }.bind(this));

        keyutil.addKeyDownListener(input, this.onKeyDown.bind(this));
        input.addEventListener('keyup', this.onKeyUp.bind(this), true);
        // cursor position affects hint severity. TODO: shortcuts for speed
        input.addEventListener('mouseup', function(ev) {
            this.isUpdating = true;
            this.update();
            this.isUpdating = false;
        }.bind(this), false);

        this.cli.addEventListener('argumentChange', this.onArgChange.bind(this));
    },

    /**
     * We need to see the output of the latest command entered
     */
    scrollOutputToBottom: function() {
        // Certain browsers have a bug such that scrollHeight is too small
        // when content does not fill the client area of the element
        var scrollHeight = Math.max(this.output.scrollHeight, this.output.clientHeight);
        this.output.scrollTop = scrollHeight - this.output.clientHeight;
    },

    /**
     * To be called on window resize or any time we want to align the elements
     * with the input box.
     */
    resizer: function() {
        var rect = this.element.getClientRects()[0];

        this.completer.style.top = rect.top + 'px';
        this.completer.style.height = rect.height + 'px';
        this.completer.style.lineHeight = rect.height + 'px';
        this.completer.style.left = rect.left + 'px';
        this.completer.style.width = rect.width + 'px';

        if (this.hintDirection.get() === 'below') {
            this.hinter.style.top = rect.bottom + 'px';
            this.hinter.style.bottom = 'auto';
        }
        else {
            this.hinter.style.top = 'auto';
            this.hinter.style.bottom = (this.win.innerHeight - rect.top) + 'px';
        }
        this.hinter.style.left = (rect.left + 30) + 'px';
        this.hinter.style.maxWidth = (rect.width - 110) + 'px';

        if (this.popupOutput) {
            if (this.outputDirection.get() === 'below') {
                this.output.style.top = rect.bottom + 'px';
                this.output.style.bottom = 'auto';
            }
            else {
                this.output.style.top = 'auto';
                this.output.style.bottom = (this.win.innerHeight - rect.top) + 'px';
            }
            this.output.style.left = rect.left + 'px';
            this.output.style.width = (rect.width - 80) + 'px';
        }
    },

    /**
     * Ensure that TAB isn't handled by the browser
     */
    onKeyDown: function(ev) {
        var handled;
        // var handled = keyboardManager.processKeyEvent(ev, this, {
        //     isCommandLine: true, isKeyUp: false
        // });
        if (ev.keyCode === keyutil.KeyHelper.KEY.TAB ||
                ev.keyCode === keyutil.KeyHelper.KEY.UP ||
                ev.keyCode === keyutil.KeyHelper.KEY.DOWN) {
            return true;
        }
        return handled;
    },

    /**
     * The main keyboard processing loop
     */
    onKeyUp: function(ev) {
        var handled;
        /*
        var handled = keyboardManager.processKeyEvent(ev, this, {
            isCommandLine: true, isKeyUp: true
        });
        */

        // RETURN does a special exec/highlight thing
        if (ev.keyCode === keyutil.KeyHelper.KEY.RETURN) {
            var worst = this.cli.getWorstHint();
            // Deny RETURN unless the command might work
            if (worst.status === Status.VALID) {
                this.cli.exec();
                this.element.value = '';
            }
            else {
                // If we've denied RETURN because the command was not VALID,
                // select the part of the command line that is causing problems
                // TODO: if there are 2 errors are we picking the right one?
                this.element.selectionStart = worst.start;
                this.element.selectionEnd = worst.end;
            }
        }

        this.update();

        // Special actions which delegate to the assignment
        var current = this.cli.getAssignmentAt(this.element.selectionStart);
        if (current) {
            // TAB does a special complete thing
            if (ev.keyCode === keyutil.KeyHelper.KEY.TAB) {
                current.complete();
                this.update();
            }

            // UP/DOWN look for some history
            if (ev.keyCode === keyutil.KeyHelper.KEY.UP) {
                current.increment();
                this.update();
            }
            if (ev.keyCode === keyutil.KeyHelper.KEY.DOWN) {
                current.decrement();
                this.update();
            }
        }

        return handled;
    },

    /**
     * Actually parse the input and make sure we're all up to date
     */
    update: function() {
        this.isUpdating = true;
        var input = {
            typed: this.element.value,
            cursor: {
                start: this.element.selectionStart,
                end: this.element.selectionEnd
            }
        };
        this.cli.update(input);

        var display = this.cli.getAssignmentAt(input.cursor.start).getHint();

        // 1. Update the completer with prompt/error marker/TAB info
        this.completer.classList.remove(Status.VALID.toString());
        this.completer.classList.remove(Status.INCOMPLETE.toString());
        this.completer.classList.remove(Status.INVALID.toString());
        // TODO: borked implementation? This is modern browser only. Fix
        // dom.removeCssClass(completer, Status.VALID.toString());
        // dom.removeCssClass(completer, Status.INCOMPLETE.toString());
        // dom.removeCssClass(completer, Status.INVALID.toString());

        var completion = '> ';
        if (this.element.value.length > 0) {
            var scores = this.cli.getInputStatusMarkup();
            completion += this.markupStatusScore(scores);
        }

        // Display the "-> prediction" at the end of the completer
        if (this.element.value.length > 0 &&
                display.predictions && display.predictions.length > 0) {
            var tab = display.predictions[0];
            completion += '  ⇥ ' + (tab.name ? tab.name : tab);
        }
        this.completer.innerHTML = completion;
        this.completer.classList.add(this.cli.getWorstHint().status.toString());
        // dom.addCssClass(input, this.cli.getWorstHint().status.toString());

        // 2. Update the hint element
        var hint = '';
        if (this.element.value.length !== 0) {
            hint += display.message;
            if (display.predictions && display.predictions.length > 0) {
                hint += ': [ ';
                display.predictions.forEach(function(prediction) {
                    hint += (prediction.name ? prediction.name : prediction);
                    hint += ' | ';
                }, this);
                hint = hint.replace(/\| $/, ']');
            }
        }

        this.hinter.innerHTML = hint;
        if (hint.length === 0) {
            this.hinter.classList.add('cptNoPopup');
        }
        else {
            this.hinter.classList.remove('cptNoPopup');
        }

        this.isUpdating = false;
    },

    /**
     * Markup an array of Status values with spans
     */
    markupStatusScore: function(scores) {
        var completion = '';
        // Create mark-up
        var i = 0;
        var lastStatus = -1;
        while (true) {
            if (lastStatus !== scores[i]) {
                completion += '';
                lastStatus = scores[i];
            }
            completion += this.element.value[i];
            i++;
            if (i === this.element.value.length) {
                completion += '';
                break;
            }
            if (lastStatus !== scores[i]) {
                completion += '';
            }
        }

        return completion;
    },

    /**
     * Update the input element to reflect the changed argument
     */
    onArgChange: function(ev) {
        if (this.isUpdating) {
            return;
        }

        var prefix = this.element.value.substring(0, ev.argument.start);
        var suffix = this.element.value.substring(ev.argument.end);
        var insert = typeof ev.text === 'string' ? ev.text : ev.text.name;
        this.element.value = prefix + insert + suffix;
        // Fix the cursor.
        var insertEnd = (prefix + insert).length;
        this.element.selectionStart = insertEnd;
        this.element.selectionEnd = insertEnd;
    }
};
exports.CliView = CliView;

});
<commit_msg>fix checking of input element in the wrong place<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Skywriter.
 *
 * The Initial Developer of the Original Code is
 * Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Joe Walker (jwalker@mozilla.com)
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

define(function(require, exports, module) {

var editorCss = require("text!cockpit/ui/cliView.css");
var dom = require("pilot/dom");
dom.importCssString(editorCss);

var canon = require("pilot/canon");
var Status = require('pilot/types').Status;
var keyutil = require('pilot/keyboard/keyutil');

var CliRequisition = require('cockpit/cli').CliRequisition;
var Hint = require('cockpit/cli').Hint;
var RequestView = require('cockpit/ui/requestView').RequestView;

var NO_HINT = new Hint(Status.VALID, '', 0, 0);

/**
 * On startup we need to:
 * 1. Add 3 sets of elements to the DOM for:
 * - command line output
 * - input hints
 * - completion
 * 2. Attach a set of events so the command line works
 */
exports.startup = function(data, reason) {
    var cli = new CliRequisition(data.env);
    var cliView = new CliView(cli, data.env);
};

/**
 * A class to handle the simplest UI implementation
 */
function CliView(cli, env) {
    this.cli = cli;
    this.doc = document;
    this.win = this.doc.defaultView;

    // TODO: we should have a better way to specify command lines???
    this.element = this.doc.getElementById('cockpitInput');
    if (!this.element) {
        console.log('No element with an id of cockpit. Bailing on cli');
        return;
    }

    this.settings = env.settings;
    this.hintDirection = this.settings.getSetting('hintDirection');
    this.outputDirection = this.settings.getSetting('outputDirection');
    this.outputHeight = this.settings.getSetting('outputHeight');

    // If the requisition tells us something has changed, we use this to know
    // if we should ignore it
    this.isUpdating = false;

    this.createElements();
    this.update();
}
CliView.prototype = {
    /**
     * Create divs for completion, hints and output
     */
    createElements: function() {
        var input = this.element;

        this.output = this.doc.getElementById('cockpitOutput');
        this.popupOutput = (this.output == null);
        if (!this.output) {
            this.output = this.doc.createElement('div');
            this.output.id = 'cockpitOutput';
            this.output.className = 'cptFocusPopup';
            input.parentNode.insertBefore(this.output, input.nextSibling);

            var setMaxOutputHeight = function() {
                this.output.style.maxHeight = this.outputHeight.get() + 'px';
            }.bind(this);
            this.outputHeight.addEventListener('change', setMaxOutputHeight);
            setMaxOutputHeight();
        }

        this.completer = this.doc.createElement('div');
        this.completer.className = 'cptCompletion VALID';
        var style = window.getComputedStyle(input);
        this.completer.style.color = style.color;
        this.completer.style.fontSize = style.fontSize;
        this.completer.style.fontFamily = style.fontFamily;
        this.completer.style.fontWeight = style.fontWeight;
        this.completer.style.fontStyle = style.fontStyle;
        input.parentNode.insertBefore(this.completer, input.nextSibling);

        // Transfer background styling to the completer.
        this.completer.style.backgroundColor = input.style.backgroundColor;
        input.style.backgroundColor = 'transparent';

        this.hinter = this.doc.createElement('div');
        this.hinter.className = 'cptHints cptFocusPopup';
        input.parentNode.insertBefore(this.hinter, input.nextSibling);

        var resizer = this.resizer.bind(this);
        this.win.addEventListener('resize', resizer, false);
        this.hintDirection.addEventListener('change', resizer);
        this.outputDirection.addEventListener('change', resizer);
        resizer();

        canon.addEventListener('output',  function(ev) {
            new RequestView(ev.request, this);
        }.bind(this));

        keyutil.addKeyDownListener(input, this.onKeyDown.bind(this));
        input.addEventListener('keyup', this.onKeyUp.bind(this), true);
        // cursor position affects hint severity. TODO: shortcuts for speed
        input.addEventListener('mouseup', function(ev) {
            this.isUpdating = true;
            this.update();
            this.isUpdating = false;
        }.bind(this), false);

        this.cli.addEventListener('argumentChange', this.onArgChange.bind(this));
    },

    /**
     * We need to see the output of the latest command entered
     */
    scrollOutputToBottom: function() {
        // Certain browsers have a bug such that scrollHeight is too small
        // when content does not fill the client area of the element
        var scrollHeight = Math.max(this.output.scrollHeight, this.output.clientHeight);
        this.output.scrollTop = scrollHeight - this.output.clientHeight;
    },

    /**
     * To be called on window resize or any time we want to align the elements
     * with the input box.
     */
    resizer: function() {
        var rect = this.element.getClientRects()[0];

        this.completer.style.top = rect.top + 'px';
        this.completer.style.height = rect.height + 'px';
        this.completer.style.lineHeight = rect.height + 'px';
        this.completer.style.left = rect.left + 'px';
        this.completer.style.width = rect.width + 'px';

        if (this.hintDirection.get() === 'below') {
            this.hinter.style.top = rect.bottom + 'px';
            this.hinter.style.bottom = 'auto';
        }
        else {
            this.hinter.style.top = 'auto';
            this.hinter.style.bottom = (this.win.innerHeight - rect.top) + 'px';
        }
        this.hinter.style.left = (rect.left + 30) + 'px';
        this.hinter.style.maxWidth = (rect.width - 110) + 'px';

        if (this.popupOutput) {
            if (this.outputDirection.get() === 'below') {
                this.output.style.top = rect.bottom + 'px';
                this.output.style.bottom = 'auto';
            }
            else {
                this.output.style.top = 'auto';
                this.output.style.bottom = (this.win.innerHeight - rect.top) + 'px';
            }
            this.output.style.left = rect.left + 'px';
            this.output.style.width = (rect.width - 80) + 'px';
        }
    },

    /**
     * Ensure that TAB isn't handled by the browser
     */
    onKeyDown: function(ev) {
        var handled;
        // var handled = keyboardManager.processKeyEvent(ev, this, {
        //     isCommandLine: true, isKeyUp: false
        // });
        if (ev.keyCode === keyutil.KeyHelper.KEY.TAB ||
                ev.keyCode === keyutil.KeyHelper.KEY.UP ||
                ev.keyCode === keyutil.KeyHelper.KEY.DOWN) {
            return true;
        }
        return handled;
    },

    /**
     * The main keyboard processing loop
     */
    onKeyUp: function(ev) {
        var handled;
        /*
        var handled = keyboardManager.processKeyEvent(ev, this, {
            isCommandLine: true, isKeyUp: true
        });
        */

        // RETURN does a special exec/highlight thing
        if (ev.keyCode === keyutil.KeyHelper.KEY.RETURN) {
            var worst = this.cli.getWorstHint();
            // Deny RETURN unless the command might work
            if (worst.status === Status.VALID) {
                this.cli.exec();
                this.element.value = '';
            }
            else {
                // If we've denied RETURN because the command was not VALID,
                // select the part of the command line that is causing problems
                // TODO: if there are 2 errors are we picking the right one?
                this.element.selectionStart = worst.start;
                this.element.selectionEnd = worst.end;
            }
        }

        this.update();

        // Special actions which delegate to the assignment
        var current = this.cli.getAssignmentAt(this.element.selectionStart);
        if (current) {
            // TAB does a special complete thing
            if (ev.keyCode === keyutil.KeyHelper.KEY.TAB) {
                current.complete();
                this.update();
            }

            // UP/DOWN look for some history
            if (ev.keyCode === keyutil.KeyHelper.KEY.UP) {
                current.increment();
                this.update();
            }
            if (ev.keyCode === keyutil.KeyHelper.KEY.DOWN) {
                current.decrement();
                this.update();
            }
        }

        return handled;
    },

    /**
     * Actually parse the input and make sure we're all up to date
     */
    update: function() {
        this.isUpdating = true;
        var input = {
            typed: this.element.value,
            cursor: {
                start: this.element.selectionStart,
                end: this.element.selectionEnd
            }
        };
        this.cli.update(input);

        var display = this.cli.getAssignmentAt(input.cursor.start).getHint();

        // 1. Update the completer with prompt/error marker/TAB info
        this.completer.classList.remove(Status.VALID.toString());
        this.completer.classList.remove(Status.INCOMPLETE.toString());
        this.completer.classList.remove(Status.INVALID.toString());
        // TODO: borked implementation? This is modern browser only. Fix
        // dom.removeCssClass(completer, Status.VALID.toString());
        // dom.removeCssClass(completer, Status.INCOMPLETE.toString());
        // dom.removeCssClass(completer, Status.INVALID.toString());

        var completion = '> ';
        if (this.element.value.length > 0) {
            var scores = this.cli.getInputStatusMarkup();
            completion += this.markupStatusScore(scores);
        }

        // Display the "-> prediction" at the end of the completer
        if (this.element.value.length > 0 &&
                display.predictions && display.predictions.length > 0) {
            var tab = display.predictions[0];
            completion += '  ⇥ ' + (tab.name ? tab.name : tab);
        }
        this.completer.innerHTML = completion;
        this.completer.classList.add(this.cli.getWorstHint().status.toString());
        // dom.addCssClass(input, this.cli.getWorstHint().status.toString());

        // 2. Update the hint element
        var hint = '';
        if (this.element.value.length !== 0) {
            hint += display.message;
            if (display.predictions && display.predictions.length > 0) {
                hint += ': [ ';
                display.predictions.forEach(function(prediction) {
                    hint += (prediction.name ? prediction.name : prediction);
                    hint += ' | ';
                }, this);
                hint = hint.replace(/\| $/, ']');
            }
        }

        this.hinter.innerHTML = hint;
        if (hint.length === 0) {
            this.hinter.classList.add('cptNoPopup');
        }
        else {
            this.hinter.classList.remove('cptNoPopup');
        }

        this.isUpdating = false;
    },

    /**
     * Markup an array of Status values with spans
     */
    markupStatusScore: function(scores) {
        var completion = '';
        // Create mark-up
        var i = 0;
        var lastStatus = -1;
        while (true) {
            if (lastStatus !== scores[i]) {
                completion += '';
                lastStatus = scores[i];
            }
            completion += this.element.value[i];
            i++;
            if (i === this.element.value.length) {
                completion += '';
                break;
            }
            if (lastStatus !== scores[i]) {
                completion += '';
            }
        }

        return completion;
    },

    /**
     * Update the input element to reflect the changed argument
     */
    onArgChange: function(ev) {
        if (this.isUpdating) {
            return;
        }

        var prefix = this.element.value.substring(0, ev.argument.start);
        var suffix = this.element.value.substring(ev.argument.end);
        var insert = typeof ev.text === 'string' ? ev.text : ev.text.name;
        this.element.value = prefix + insert + suffix;
        // Fix the cursor.
        var insertEnd = (prefix + insert).length;
        this.element.selectionStart = insertEnd;
        this.element.selectionEnd = insertEnd;
    }
};
exports.CliView = CliView;

});
<|endoftext|>


<commit_before>'use babel';
import fs from 'fs';
import path from 'path';
import {CompositeDisposable} from 'atom';
import mkdirp from 'mkdirp';
import coffee from 'coffee-script';
import uglifyJS from 'uglify-js';

export var config = {
  compileIfNoOutSpecified: {
    description: 'compile coffeescript files where no "out" parameter is specified at the top of the file',
    type: 'boolean',
    default: false,
  },
};

var disposables;

export function activate() {
  disposables = new CompositeDisposable(
    atom.commands.add('atom-text-editor', 'core:save', event => handleSave(event.currentTarget.getModel())),
  );
}

export function deactivate() {
  disposables.dispose();
  disposables = null;
}

const coffeeScriptGrammar = atom.grammars.grammarForScopeName('source.coffee');

function handleSave(textEditor) {
  if(textEditor.getGrammar() == coffeeScriptGrammar) {
    compile(textEditor);
  }
}

function compile(textEditor) {
  var params = getParams(textEditor);
  if(!params.out && !atom.config.get('coffee-autocompile.compileIfNoOutSpecified'))
    return;
  params.inPath = textEditor.getURI();
  params.outPath = params.out ?
    path.resolve(path.dirname(params.inPath), params.out) :
    params.inPath.replace(/\.coffee$/, '.js');
  render(textEditor.getText(), params);
}

function render(source, params) {
  var renderer = coffee.compile(source, {
    sourceMap: !!params.sourcemap,
  });
  var js;
  var sourcemap;

  if(params.sourcemap) {
    ({js, sourcemap} = postRenderWithSourcemap(renderer, params));
    writeFile(`${params.outPath}.map`, sourcemap);
  } else {
    js = postRenderNoSourcemap(renderer, params);
  }

  writeFile(params.outPath, js);
}

function postRenderWithSourcemap(renderer, {outPath, compress}) {
  var {js, v3SourceMap: sourcemap} = renderer;
  var outSourcemapPath = `${path.basename(outPath)}.map`;

  if(compress) {
    ({code: js, map: sourcemap} = uglifyJS.minify(js, {
      fromString: true,
      inSourceMap: JSON.parse(sourcemap),
      outSourceMap: outSourcemapPath,
    }));
  } else {
    js += `\n//# sourceMappingURL=${outSourcemapPath}`;
  }
  return {js, sourcemap};
}

function postRenderNoSourcemap(js, {compress}) {
  return !compress ? js : uglifyJS.minify(js, {
    fromString: true,
  }).code;
}

function writeFile(filePath, content) {
  mkdirp(path.dirname(filePath), error => {
    if(error) return atom.notifications.addError(error.message);

    fs.writeFile(filePath, content, error => {
      if(error) return atom.notifications.addError(error.message);

      atom.notifications.addSuccess(`Saved to ${filePath}`);
    });
  });
}

function getParams(textEditor) {
  var firstLine = textEditor.lineTextForBufferRow(0);
  if(firstLine.match(/^#!/)) firstLine = textEditor.lineTextForBufferRow(1);

  var [, paramString] = firstLine.match(/\s*#\s*(.*)/) || [];
  if(!paramString) return {};
  paramString = paramString.trim();
  var out = getParam(paramString, 'out');
  var compress = getParam(paramString, 'compress', true);
  var sourcemap = getParam(paramString, 'sourcemap', true);
  return {out, compress, sourcemap};
}

function getParam(paramString, key, isBool = false) {
  var regex = new RegExp(`${key}\s*:\s*([^,]+)`);
  var [, value] = paramString.match(regex) || [];
  if(!value) return '';
  value = value.trim();
  if(isBool) value = parseBool(value);
  return value;
}

function parseBool(value) {
  return value == 'false' ? false : !!value;
}
<commit_msg>use loophole to prevent uglify-js from crashing<commit_after>'use babel';
import fs from 'fs';
import path from 'path';
import {CompositeDisposable} from 'atom';
import mkdirp from 'mkdirp';
import coffee from 'coffee-script';
import {allowUnsafeNewFunction, allowUnsafeEval} from 'loophole';

var uglifyJS;
allowUnsafeNewFunction(() =>
  uglifyJS = require('uglify-js')
);

export var config = {
  compileIfNoOutSpecified: {
    description: 'compile coffeescript files where no "out" parameter is specified at the top of the file',
    type: 'boolean',
    default: false,
  },
};

var disposables;

export function activate() {
  disposables = new CompositeDisposable(
    atom.commands.add('atom-text-editor', 'core:save', event => handleSave(event.currentTarget.getModel())),
  );
}

export function deactivate() {
  disposables.dispose();
  disposables = null;
}

const coffeeScriptGrammar = atom.grammars.grammarForScopeName('source.coffee');

function handleSave(textEditor) {
  if(textEditor.getGrammar() == coffeeScriptGrammar) {
    compile(textEditor);
  }
}

function compile(textEditor) {
  var params = getParams(textEditor);
  if(!params.out && !atom.config.get('coffee-autocompile.compileIfNoOutSpecified'))
    return;
  params.inPath = textEditor.getURI();
  params.outPath = params.out ?
    path.resolve(path.dirname(params.inPath), params.out) :
    params.inPath.replace(/\.coffee$/, '.js');
  render(textEditor.getText(), params);
}

function render(source, params) {
  var renderer = coffee.compile(source, {
    sourceMap: !!params.sourcemap,
  });
  var js;
  var sourcemap;

  allowUnsafeNewFunction(() => {
    if(params.sourcemap) {
      ({js, sourcemap} = postRenderWithSourcemap(renderer, params));
      writeFile(`${params.outPath}.map`, sourcemap);
    } else {
      js = postRenderNoSourcemap(renderer, params);
    }
  });

  writeFile(params.outPath, js);
}

function postRenderWithSourcemap(renderer, {outPath, compress}) {
  var {js, v3SourceMap: sourcemap} = renderer;
  var outSourcemapPath = `${path.basename(outPath)}.map`;

  if(compress) {
    ({code: js, map: sourcemap} = uglifyJS.minify(js, {
      fromString: true,
      inSourceMap: JSON.parse(sourcemap),
      outSourceMap: outSourcemapPath,
    }));
  } else {
    js += `\n//# sourceMappingURL=${outSourcemapPath}`;
  }
  return {js, sourcemap};
}

function postRenderNoSourcemap(js, {compress}) {
  return !compress ? js : uglifyJS.minify(js, {
    fromString: true,
  }).code;
}

function writeFile(filePath, content) {
  mkdirp(path.dirname(filePath), error => {
    if(error) return atom.notifications.addError(error.message);

    fs.writeFile(filePath, content, error => {
      if(error) return atom.notifications.addError(error.message);

      atom.notifications.addSuccess(`Saved to ${filePath}`);
    });
  });
}

function getParams(textEditor) {
  var firstLine = textEditor.lineTextForBufferRow(0);
  if(firstLine.match(/^#!/)) firstLine = textEditor.lineTextForBufferRow(1);

  var [, paramString] = firstLine.match(/\s*#\s*(.*)/) || [];
  if(!paramString) return {};
  paramString = paramString.trim();
  var out = getParam(paramString, 'out');
  var compress = getParam(paramString, 'compress', true);
  var sourcemap = getParam(paramString, 'sourcemap', true);
  return {out, compress, sourcemap};
}

function getParam(paramString, key, isBool = false) {
  var regex = new RegExp(`${key}\s*:\s*([^,]+)`);
  var [, value] = paramString.match(regex) || [];
  if(!value) return '';
  value = value.trim();
  if(isBool) value = parseBool(value);
  return value;
}

function parseBool(value) {
  return value == 'false' ? false : !!value;
}
<|endoftext|>


<commit_before>const Board = require('./board')

function vertex2key(v) {
    return v.join(',')
}

function hasNLiberties(board, vertex, N, visited = [], count = 0, sign = null) {
    if (sign == null) sign = board.arrangement[vertex2key(vertex)]

    if (visited.some(v => v[0] == vertex[0] && v[1] == vertex[1]))
        return false

    let neighbors = board.getNeighbors(vertex)
    let freeNeighbors = []
    let friendlyNeighbors = []

    for (let i = 0; i < neighbors.length; i++) {
        let n = neighbors[i]
        let s = board.arrangement[vertex2key(n)]

        if (s == 0) freeNeighbors.push(n)
        else if (s == sign) friendlyNeighbors.push(n)
    }

    count += freeNeighbors.length
    if (count >= N) return true

    visited.push(vertex)

    return friendlyNeighbors.some(n => hasNLiberties(board, n, N, visited, count, sign))
}

function makeMove(board, sign, vertex) {
    let neighbors = board.getNeighbors(vertex)
    let neighborSigns = neighbors.map(n => board.arrangement[vertex2key(n)])

    if (neighborSigns.every(s => s == sign)) {
        return null
    }

    let key = vertex2key(vertex)
    board.arrangement[key] = sign

    if (!hasNLiberties(board, vertex, 2)) {
        board.arrangement[key] = 0
        return null
    }

    let dead = []

    for (let i = 0; i < neighbors.length; i++) {
        let n = neighbors[i]
        if (neighborSigns[i] != -sign || hasNLiberties(board, n, 1))
            continue

        let chain = board.getChain(n)
        dead.push(...chain)
        chain.forEach(c => board.arrangement[c] = 0)
    }

    return dead
}

function fixHoles(board) {
    for (let x = 0; x < board.width; x++) {
        for (let y = 0; y < board.height; y++) {
            let vertex = [x, y]

            if (board.arrangement[vertex2key(vertex)] != 0)
                continue

            let neighbors = board.getNeighbors(vertex)
            let sign = board.arrangement[vertex2key(neighbors[0])]
            let fix = true

            for (let i = 1; i < neighbors.length; i++) {
                let n = neighbors[i]

                if (board.arrangement[vertex2key(n)] != sign) {
                    fix = false
                    break
                }
            }

            if (fix) board.arrangement[vertex] = sign
        }
    }

    return board
}

exports.guess = function(board, ...args) {
    let map = exports.getProbabilityMap(board, ...args)
    let done = {}
    let result = []

    for (let x = 0; x < board.width; x++) {
        for (let y = 0; y < board.height; y++) {
            let vertex = [x, y]
            let sign = board.arrangement[vertex]

            if (sign == 0 || vertex in done) continue

            let chain = board.getChain(vertex)
            let probability = chain.map(v => map[v]).reduce((x, sum) => sum + x) / chain.length
            let newSign = probability < 0.5 ? -1 : probability > 0.5 ? 1 : 0

            if (newSign == -sign) result.push(...chain)

            done[vertex] = true
        }
    }

    return result
}

exports.playTillEnd = function(board, sign, iterations = null) {
    if (iterations == null) iterations = board.width * board.height
    board = board.clone()

    let freeVertices = []
    let illegalVertices = []

    for (let x = 0; x < board.width; x++) {
        for (let y = 0; y < board.height; y++) {
            if (board.arrangement[[x, y]] != 0) continue
            freeVertices.push([x, y])
        }
    }

    let finished = {'-1': false, '1': false}

    while (iterations > 0) {
        if (freeVertices.length == 0 || finished[-sign] && finished[sign])
            break

        let madeMove = false

        while (freeVertices.length > 0) {
            let randomIndex = Math.floor(Math.random() * freeVertices.length)
            let vertex = freeVertices[randomIndex]
            let freedVertices = makeMove(board, sign, vertex, false)

            freeVertices.splice(randomIndex, 1)

            if (freedVertices != null) {
                freeVertices.push(...freedVertices)

                finished[-sign] = false
                madeMove = true

                break
            } else {
                illegalVertices.push(vertex)
            }
        }

        finished[sign] = !madeMove

        freeVertices.push(...illegalVertices)
        illegalVertices.length = 0

        sign = -sign
        iterations--
    }

    return fixHoles(board).arrangement
}

exports.getProbabilityMap = function(board, iterations = 30) {
    let pmap = {}
    let nmap = {}
    let result = {}

    for (let i = 0; i < iterations; i++) {
        let sign = Math.sign(Math.random() - 0.5)
        let areaMap = exports.playTillEnd(board, sign)

        for (let v in areaMap) {
            if (!(v in pmap)) pmap[v] = 0
            if (!(v in nmap)) nmap[v] = 0

            if (areaMap[v] < 0) nmap[v]++
            else if (areaMap[v] > 0) pmap[v]++
        }
    }

    for (let v in pmap) {
        if (pmap[v] + nmap[v] == 0) result[v] = 0.5
        else result[v] = pmap[v] / (pmap[v] + nmap[v])
    }

    return result
}
<commit_msg>Update deadstones to accomodate Board.(get|set)()<commit_after>const Board = require('./board')

function vertex2key(v) {
    return v.join(',')
}

function hasNLiberties(board, vertex, N, visited = [], count = 0, sign = null) {
    if (sign == null) sign = board.get(vertex2key(vertex))

    if (visited.some(v => v[0] == vertex[0] && v[1] == vertex[1]))
        return false

    let neighbors = board.getNeighbors(vertex)
    let freeNeighbors = []
    let friendlyNeighbors = []

    for (let i = 0; i < neighbors.length; i++) {
        let n = neighbors[i]
        let s = board.get(vertex2key(n))

        if (s == 0) freeNeighbors.push(n)
        else if (s == sign) friendlyNeighbors.push(n)
    }

    count += freeNeighbors.length
    if (count >= N) return true

    visited.push(vertex)

    return friendlyNeighbors.some(n => hasNLiberties(board, n, N, visited, count, sign))
}

function makeMove(board, sign, vertex) {
    let neighbors = board.getNeighbors(vertex)
    let neighborSigns = neighbors.map(n => board.get(vertex2key(n)))

    if (neighborSigns.every(s => s == sign)) {
        return null
    }

    let key = vertex2key(vertex)
    board.set(key, sign)

    if (!hasNLiberties(board, vertex, 2)) {
        board.set(key, 0)
        return null
    }

    let dead = []

    for (let i = 0; i < neighbors.length; i++) {
        let n = neighbors[i]
        if (neighborSigns[i] != -sign || hasNLiberties(board, n, 1))
            continue

        let chain = board.getChain(n)
        dead.push(...chain)
        chain.forEach(c => board.set(c, 0))
    }

    return dead
}

function fixHoles(board) {
    for (let x = 0; x < board.width; x++) {
        for (let y = 0; y < board.height; y++) {
            let vertex = [x, y]

            if (board.get(vertex2key(vertex)) != 0)
                continue

            let neighbors = board.getNeighbors(vertex)
            let sign = board.get(vertex2key(neighbors[0]))
            let fix = true

            for (let i = 1; i < neighbors.length; i++) {
                let n = neighbors[i]

                if (board.get(vertex2key(n)) != sign) {
                    fix = false
                    break
                }
            }

            if (fix) board.get(vertex) = sign
        }
    }

    return board
}

exports.guess = function(board, ...args) {
    let map = exports.getProbabilityMap(board, ...args)
    let done = {}
    let result = []

    for (let x = 0; x < board.width; x++) {
        for (let y = 0; y < board.height; y++) {
            let vertex = [x, y]
            let sign = board.get(vertex)

            if (sign == 0 || vertex in done) continue

            let chain = board.getChain(vertex)
            let probability = chain.map(v => map[v]).reduce((x, sum) => sum + x) / chain.length
            let newSign = probability < 0.5 ? -1 : probability > 0.5 ? 1 : 0

            if (newSign == -sign) result.push(...chain)

            done[vertex] = true
        }
    }

    return result
}

exports.playTillEnd = function(board, sign, iterations = null) {
    if (iterations == null) iterations = board.width * board.height
    board = board.clone()

    let freeVertices = []
    let illegalVertices = []

    for (let x = 0; x < board.width; x++) {
        for (let y = 0; y < board.height; y++) {
            if (board.get([x, y]) != 0) continue
            freeVertices.push([x, y])
        }
    }

    let finished = {'-1': false, '1': false}

    while (iterations > 0) {
        if (freeVertices.length == 0 || finished[-sign] && finished[sign])
            break

        let madeMove = false

        while (freeVertices.length > 0) {
            let randomIndex = Math.floor(Math.random() * freeVertices.length)
            let vertex = freeVertices[randomIndex]
            let freedVertices = makeMove(board, sign, vertex, false)

            freeVertices.splice(randomIndex, 1)

            if (freedVertices != null) {
                freeVertices.push(...freedVertices)

                finished[-sign] = false
                madeMove = true

                break
            } else {
                illegalVertices.push(vertex)
            }
        }

        finished[sign] = !madeMove

        freeVertices.push(...illegalVertices)
        illegalVertices.length = 0

        sign = -sign
        iterations--
    }

    return fixHoles(board).arrangement
}

exports.getProbabilityMap = function(board, iterations = 30) {
    let pmap = {}
    let nmap = {}
    let result = {}

    for (let i = 0; i < iterations; i++) {
        let sign = Math.sign(Math.random() - 0.5)
        let areaMap = exports.playTillEnd(board, sign)

        for (let v in areaMap) {
            if (!(v in pmap)) pmap[v] = 0
            if (!(v in nmap)) nmap[v] = 0

            if (areaMap[v] < 0) nmap[v]++
            else if (areaMap[v] > 0) pmap[v]++
        }
    }

    for (let v in pmap) {
        if (pmap[v] + nmap[v] == 0) result[v] = 0.5
        else result[v] = pmap[v] / (pmap[v] + nmap[v])
    }

    return result
}
<|endoftext|>