<commit_before>openerp.web.search = function(instance) {
var QWeb = instance.web.qweb,
      _t =  instance.web._t,
     _lt = instance.web._lt;
_.mixin({
    sum: function (obj) { return _.reduce(obj, function (a, b) { return a + b; }, 0); }
});

/** @namespace */
var my = instance.web.search = {};

var B = Backbone;
my.FacetValue = B.Model.extend({

});
my.FacetValues = B.Collection.extend({
    model: my.FacetValue
});
my.Facet = B.Model.extend({
    initialize: function (attrs) {
        var values = attrs.values;
        delete attrs.values;

        B.Model.prototype.initialize.apply(this, arguments);

        this.values = new my.FacetValues(values || []);
        this.values.on('add remove change reset', function () {
            this.trigger('change', this);
        }, this);
    },
    get: function (key) {
        if (key !== 'values') {
            return B.Model.prototype.get.call(this, key);
        }
        return this.values.toJSON();
    },
    set: function (key, value) {
        if (key !== 'values') {
            return B.Model.prototype.set.call(this, key, value);
        }
        this.values.reset(value);
    },
    toJSON: function () {
        var out = {};
        var attrs = this.attributes;
        for(var att in attrs) {
            if (!attrs.hasOwnProperty(att) || att === 'field') {
                continue;
            }
            out[att] = attrs[att];
        }
        out.values = this.values.toJSON();
        return out;
    }
});
my.SearchQuery = B.Collection.extend({
    model: my.Facet,
    initialize: function () {
        B.Collection.prototype.initialize.apply(
            this, arguments);
        this.on('change', function (facet) {
            if(!facet.values.isEmpty()) { return; }

            this.remove(facet);
        }, this);
    },
    add: function (values, options) {
        options || (options = {});
        if (!(values instanceof Array)) {
            values = [values];
        }

        _(values).each(function (value) {
            var model = this._prepareModel(value, options);
            var previous = this.detect(function (facet) {
                return facet.get('category') === model.get('category')
                    && facet.get('field') === model.get('field');
            });
            if (previous) {
                previous.values.add(model.get('values'));
                return;
            }
            B.Collection.prototype.add.call(this, model, options);
        }, this);
        return this;
    },
    toggle: function (value, options) {
        options || (options = {});

        var facet = this.detect(function (facet) {
            return facet.get('category') === value.category
                && facet.get('field') === value.field;
        });
        if (!facet) {
            return this.add(value, options);
        }

        var changed = false;
        _(value.values).each(function (val) {
            var already_value = facet.values.detect(function (v) {
                return v.get('value') === val.value
                    && v.get('label') === val.label;
            });
            // toggle value
            if (already_value) {
                facet.values.remove(already_value, {silent: true});
            } else {
                facet.values.add(val, {silent: true});
            }
            changed = true;
        });
        // "Commit" changes to values array as a single call, so observers of
        // change event don't get misled by intermediate incomplete toggling
        // states
        facet.trigger('change', facet);
        return this;
    }
});

my.InputView = instance.web.Widget.extend({
    template: 'SearchView.InputView',
    start: function () {
        var self = this;
        var p = this._super.apply(this, arguments);
        this.$element.on('focus', function () {
            self.$element.siblings().trigger('deselect');
        });
        return p;
    }
});
my.FacetView = instance.web.Widget.extend({
    template: 'SearchView.FacetView',
    init: function (parent, model) {
        this._super(parent);
        this.model = model;
        this.model.on('change', this.model_changed, this);
    },
    destroy: function () {
        this.model.off('change', this.model_changed, this);
        this._super();
    },
    start: function () {
        var self = this;
        this.$element.on('deselect', function () {
            self.$element.removeClass('oe_selected');
        });
        this.$element.on('click', function (e) {
            if ($(e.target).is('.oe_facet_remove')) {
                return;
            }
            self.$element.siblings().trigger('deselect');
            self.$element.addClass('oe_selected');
        });
        var $e = self.$element.find('> span:last-child');
        var q = $.when(this._super());
        return q.pipe(function () {
            var values = self.model.values.map(function (value) {
                return new my.FacetValueView(self, value).appendTo($e);
            });

            return $.when.apply(null, values);
        });
    },
    model_changed: function () {
        this.$element.text(this.$element.text() + '*');
    }
});
my.FacetValueView = instance.web.Widget.extend({
    template: 'SearchView.FacetView.Value',
    init: function (parent, model) {
        this._super(parent);
        this.model = model;
        this.model.on('change', this.model_changed, this);
    },
    destroy: function () {
        this.model.off('change', this.model_changed, this);
        this._super();
    },
    model_changed: function () {
        this.$element.text(this.$element.text() + '*');
    }
});

instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.SearchView# */{
    template: "SearchView",
    /**
     * @constructs instance.web.SearchView
     * @extends instance.web.Widget
     *
     * @param parent
     * @param dataset
     * @param view_id
     * @param defaults
     * @param hidden
     */
    init: function(parent, dataset, view_id, defaults, hidden) {
        this._super(parent);
        this.dataset = dataset;
        this.model = dataset.model;
        this.view_id = view_id;

        this.defaults = defaults || {};
        this.has_defaults = !_.isEmpty(this.defaults);

        this.inputs = [];
        this.controls = {};

        this.hidden = !!hidden;
        this.headless = this.hidden && !this.has_defaults;

        this.filter_data = {};

        this.input_subviews = [];

        this.ready = $.Deferred();
    },
    start: function() {
        var self = this;
        var p = this._super();

        this.setup_global_completion();
        this.query = new my.SearchQuery()
                .on('add change reset', this.proxy('do_search'))
                .on('add change reset', this.proxy('renderFacets'))
                .on('remove', function (record, collection, options) {
            self.renderFacets();
            if (options.trigger_search) {
                self.do_search();
            }
        });

        if (this.hidden) {
            this.$element.hide();
        }
        if (this.headless) {
            this.ready.resolve();
        } else {
            var load_view = this.rpc("/web/searchview/load", {
                model: this.model,
                view_id: this.view_id,
                context: this.dataset.get_context() });
            // FIXME: local eval of domain and context to get rid of special endpoint
            var filters = this.rpc('/web/searchview/get_filters', {
                model: this.model
            }).then(function (filters) { self.custom_filters = filters; });

            $.when(load_view, filters)
                .pipe(function (load) { return load[0]; })
                .pipe(this.on_loaded)
                .fail(function () {
                    self.ready.reject.apply(null, arguments);
                });
        }

        this.$element.on('click', '.oe_searchview_unfold_drawer', function () {
            self.$element.toggleClass('oe_searchview_open_drawer');
        });
        this.$element.on('click', '.oe_facet_remove', function (e) {
            e.stopImmediatePropagation();
            // get index of clicked facet: number of preceding facet siblings
            var index = $(this).closest('.oe_searchview_facet')
                   .prevAll('.oe_searchview_facet')
                   .length;
            self.query.remove(
                self.query.at(index), {trigger_search: true});
        });

        return $.when(p, this.ready);
    },
    show: function () {
        this.$element.show();
    },
    hide: function () {
        this.$element.hide();
    },

    /**
     * Sets up thingie where all the mess is put?
     */
    select_for_drawer: function () {
        return _(this.inputs).filter(function (input) {
            return input.in_drawer();
        });
    },
    /**
     * Sets up search view's view-wide auto-completion widget
     */
    setup_global_completion: function () {
        // Prevent keydown from within a facet's input from reaching the
        // auto-completion widget and opening the completion list
        this.$element.on('keydown', '.search_facet input', function (e) {
            e.stopImmediatePropagation();
        });

        this.$element.autocomplete({
            source: this.proxy('complete_global_search'),
            select: this.proxy('select_completion'),
            focus: function (e) { e.preventDefault(); },
            html: true,
            minLength: 0,
            delay: 0
        }).data('autocomplete')._renderItem = function (ul, item) {
            // item of completion list
            var $item = $( "<li></li>" )
                .data( "item.autocomplete", item )
                .appendTo( ul );

            if (item.value !== undefined) {
                // regular completion item
                return $item.append(
                    (item.label)
                        ? $('<a>').html(item.label)
                        : $('<a>').text(item.value));
            }
            return $item.text(item.category)
                .css({
                    borderTop: '1px solid #cccccc',
                    margin: 0,
                    padding: 0,
                    zoom: 1,
                    'float': 'left',
                    clear: 'left',
                    width: '100%'
                });
        }
    },
    /**
     * Provide auto-completion result for req.term (an array to `resp`)
     *
     * @param {Object} req request to complete
     * @param {String} req.term searched term to complete
     * @param {Function} resp response callback
     */<commit_msg>[ADD] autocompletion correctly using input values<commit_after>openerp.web.search = function(instance) {
var QWeb = instance.web.qweb,
      _t =  instance.web._t,
     _lt = instance.web._lt;
_.mixin({
    sum: function (obj) { return _.reduce(obj, function (a, b) { return a + b; }, 0); }
});

/** @namespace */
var my = instance.web.search = {};

var B = Backbone;
my.FacetValue = B.Model.extend({

});
my.FacetValues = B.Collection.extend({
    model: my.FacetValue
});
my.Facet = B.Model.extend({
    initialize: function (attrs) {
        var values = attrs.values;
        delete attrs.values;

        B.Model.prototype.initialize.apply(this, arguments);

        this.values = new my.FacetValues(values || []);
        this.values.on('add remove change reset', function () {
            this.trigger('change', this);
        }, this);
    },
    get: function (key) {
        if (key !== 'values') {
            return B.Model.prototype.get.call(this, key);
        }
        return this.values.toJSON();
    },
    set: function (key, value) {
        if (key !== 'values') {
            return B.Model.prototype.set.call(this, key, value);
        }
        this.values.reset(value);
    },
    toJSON: function () {
        var out = {};
        var attrs = this.attributes;
        for(var att in attrs) {
            if (!attrs.hasOwnProperty(att) || att === 'field') {
                continue;
            }
            out[att] = attrs[att];
        }
        out.values = this.values.toJSON();
        return out;
    }
});
my.SearchQuery = B.Collection.extend({
    model: my.Facet,
    initialize: function () {
        B.Collection.prototype.initialize.apply(
            this, arguments);
        this.on('change', function (facet) {
            if(!facet.values.isEmpty()) { return; }

            this.remove(facet);
        }, this);
    },
    add: function (values, options) {
        options || (options = {});
        if (!(values instanceof Array)) {
            values = [values];
        }

        _(values).each(function (value) {
            var model = this._prepareModel(value, options);
            var previous = this.detect(function (facet) {
                return facet.get('category') === model.get('category')
                    && facet.get('field') === model.get('field');
            });
            if (previous) {
                previous.values.add(model.get('values'));
                return;
            }
            B.Collection.prototype.add.call(this, model, options);
        }, this);
        return this;
    },
    toggle: function (value, options) {
        options || (options = {});

        var facet = this.detect(function (facet) {
            return facet.get('category') === value.category
                && facet.get('field') === value.field;
        });
        if (!facet) {
            return this.add(value, options);
        }

        var changed = false;
        _(value.values).each(function (val) {
            var already_value = facet.values.detect(function (v) {
                return v.get('value') === val.value
                    && v.get('label') === val.label;
            });
            // toggle value
            if (already_value) {
                facet.values.remove(already_value, {silent: true});
            } else {
                facet.values.add(val, {silent: true});
            }
            changed = true;
        });
        // "Commit" changes to values array as a single call, so observers of
        // change event don't get misled by intermediate incomplete toggling
        // states
        facet.trigger('change', facet);
        return this;
    }
});

my.InputView = instance.web.Widget.extend({
    template: 'SearchView.InputView',
    start: function () {
        var self = this;
        var p = this._super.apply(this, arguments);
        this.$element.on('focus', function () {
            self.$element.siblings().trigger('deselect');
        });
        return p;
    }
});
my.FacetView = instance.web.Widget.extend({
    template: 'SearchView.FacetView',
    init: function (parent, model) {
        this._super(parent);
        this.model = model;
        this.model.on('change', this.model_changed, this);
    },
    destroy: function () {
        this.model.off('change', this.model_changed, this);
        this._super();
    },
    start: function () {
        var self = this;
        this.$element.on('deselect', function () {
            self.$element.removeClass('oe_selected');
        });
        this.$element.on('click', function (e) {
            if ($(e.target).is('.oe_facet_remove')) {
                return;
            }
            self.$element.siblings().trigger('deselect');
            self.$element.addClass('oe_selected');
        });
        var $e = self.$element.find('> span:last-child');
        var q = $.when(this._super());
        return q.pipe(function () {
            var values = self.model.values.map(function (value) {
                return new my.FacetValueView(self, value).appendTo($e);
            });

            return $.when.apply(null, values);
        });
    },
    model_changed: function () {
        this.$element.text(this.$element.text() + '*');
    }
});
my.FacetValueView = instance.web.Widget.extend({
    template: 'SearchView.FacetView.Value',
    init: function (parent, model) {
        this._super(parent);
        this.model = model;
        this.model.on('change', this.model_changed, this);
    },
    destroy: function () {
        this.model.off('change', this.model_changed, this);
        this._super();
    },
    model_changed: function () {
        this.$element.text(this.$element.text() + '*');
    }
});

instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.SearchView# */{
    template: "SearchView",
    /**
     * @constructs instance.web.SearchView
     * @extends instance.web.Widget
     *
     * @param parent
     * @param dataset
     * @param view_id
     * @param defaults
     * @param hidden
     */
    init: function(parent, dataset, view_id, defaults, hidden) {
        this._super(parent);
        this.dataset = dataset;
        this.model = dataset.model;
        this.view_id = view_id;

        this.defaults = defaults || {};
        this.has_defaults = !_.isEmpty(this.defaults);

        this.inputs = [];
        this.controls = {};

        this.hidden = !!hidden;
        this.headless = this.hidden && !this.has_defaults;

        this.filter_data = {};

        this.input_subviews = [];

        this.ready = $.Deferred();
    },
    start: function() {
        var self = this;
        var p = this._super();

        this.setup_global_completion();
        this.query = new my.SearchQuery()
                .on('add change reset', this.proxy('do_search'))
                .on('add change reset', this.proxy('renderFacets'))
                .on('remove', function (record, collection, options) {
            self.renderFacets();
            if (options.trigger_search) {
                self.do_search();
            }
        });

        if (this.hidden) {
            this.$element.hide();
        }
        if (this.headless) {
            this.ready.resolve();
        } else {
            var load_view = this.rpc("/web/searchview/load", {
                model: this.model,
                view_id: this.view_id,
                context: this.dataset.get_context() });
            // FIXME: local eval of domain and context to get rid of special endpoint
            var filters = this.rpc('/web/searchview/get_filters', {
                model: this.model
            }).then(function (filters) { self.custom_filters = filters; });

            $.when(load_view, filters)
                .pipe(function (load) { return load[0]; })
                .pipe(this.on_loaded)
                .fail(function () {
                    self.ready.reject.apply(null, arguments);
                });
        }

        this.$element.on('click', '.oe_searchview_unfold_drawer', function () {
            self.$element.toggleClass('oe_searchview_open_drawer');
        });
        this.$element.on('click', '.oe_facet_remove', function (e) {
            e.stopImmediatePropagation();
            // get index of clicked facet: number of preceding facet siblings
            var index = $(this).closest('.oe_searchview_facet')
                   .prevAll('.oe_searchview_facet')
                   .length;
            self.query.remove(
                self.query.at(index), {trigger_search: true});
        });

        return $.when(p, this.ready);
    },
    show: function () {
        this.$element.show();
    },
    hide: function () {
        this.$element.hide();
    },

    /**
     * Sets up thingie where all the mess is put?
     */
    select_for_drawer: function () {
        return _(this.inputs).filter(function (input) {
            return input.in_drawer();
        });
    },
    /**
     * Sets up search view's view-wide auto-completion widget
     */
    setup_global_completion: function () {
        var self = this;

        // autocomplete only correctly handles being initialized on the actual
        // editable element (and only an element with a @value in 1.8 e.g.
        // input or textarea), cheat by setting val() on $element
        this.$element.on('keydown', function () {
            // keydown is triggered *before* the element's value is set, so
            // delay this. Pray that setTimeout are executed in FIFO (if they
            // have the same delay) as autocomplete uses the exact same trick.
            // FIXME: brittle as fuck
            setTimeout(function () {
                self.$element.val(self.currentInputValue());
            }, 0);

        });

        var completer = this.$element.autocomplete({
            source: this.proxy('complete_global_search'),
            select: this.proxy('select_completion'),
            focus: function (e) { e.preventDefault(); },
            html: true,
            minLength: 0,
            delay: 0
        }).data('autocomplete')._renderItem = function (ul, item) {
            // item of completion list
            var $item = $( "<li></li>" )
                .data( "item.autocomplete", item )
                .appendTo( ul );

            if (item.facet !== undefined) {
                // regular completion item
                return $item.append(
                    (item.label)
                        ? $('<a>').html(item.label)
                        : $('<a>').text(item.value));
            }
            return $item.text(item.label)
                .css({
                    borderTop: '1px solid #cccccc',
                    margin: 0,
                    padding: 0,
                    zoom: 1,
                    'float': 'left',
                    clear: 'left',
                    width: '100%'
                });
        };
    },
    /**
     * Gets value out of the currently focused "input" (a
     * div[contenteditable].oe_searchview_input)
     */
    currentInputValue: function () {
        return this.$element.find('div.oe_searchview_input:focus').text();
    },
    /**
     * Provide auto-completion result for req.term (an array to `resp`)
     *
     * @param {Object} req request to complete
     * @param {String} req.term searched term to complete
     * @param {Function} resp response callback
     */
{'fraction_non_alphanumeric': 0.14369063772048846, 'fraction_numerical': 0.0011307100859339666, 'mean_word_length': 1.7190113133300542, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '227496', 'n_tokens_mistral': 6239, 'n_tokens_neox': 6051, 'n_words': 1326}