<commit_before>import angular from 'angular';
import _ from 'underscore';
import template from './dimSearchFilter.directive.html';
import Textcomplete from 'textcomplete/lib/textcomplete';
import Textarea from 'textcomplete/lib/textarea';
import { flatMap } from '../util';

angular.module('dimApp')
  .factory('dimSearchService', SearchService)
  .directive('dimSearchFilter', SearchFilter);

function SearchService(dimSettingsService, dimCategory, D2Categories) {
  const categoryFilters = {
    pulserifle: ['CATEGORY_PULSE_RIFLE'],
    scoutrifle: ['CATEGORY_SCOUT_RIFLE'],
    handcannon: ['CATEGORY_HAND_CANNON'],
    autorifle: ['CATEGORY_AUTO_RIFLE'],
    sniperrifle: ['CATEGORY_SNIPER_RIFLE'],
    shotgun: ['CATEGORY_SHOTGUN'],
    sidearm: ['CATEGORY_SIDEARM'],
    rocketlauncher: ['CATEGORY_ROCKET_LAUNCHER'],
    fusionrifle: ['CATEGORY_FUSION_RIFLE'],
    sword: ['CATEGORY_SWORD'],
  };

  const itemTypes = [];

  const stats = ['charge', 'impact', 'range', 'stability', 'reload', 'magazine', 'aimassist', 'equipspeed'];

  // don't have access to dimSettingService yet here.
  if (dimSettingsService.destinyVersion === 1) {
    Object.assign(categoryFilters, {
      primaryweaponengram: ['CATEGORY_PRIMARY_WEAPON', 'CATEGORY_ENGRAM'],
      specialweaponengram: ['CATEGORY_SPECIAL_WEAPON', 'CATEGORY_ENGRAM'],
      heavyweaponengram: ['CATEGORY_HEAVY_WEAPON', 'CATEGORY_ENGRAM'],
      machinegun: ['CATEGORY_MACHINE_GUN'],
    });
    itemTypes.push(...flatMap(dimCategory, (l) => _.map(l, (v) => v.toLowerCase())));
    stats.push(...['rof']);
  } else {
    Object.assign(categoryFilters, {
      grenadelauncher: ['CATEGORY_GRENADE_LAUNCHER'],
      submachine: ['CATEGORY_SUBMACHINEGUN'],
    });
    itemTypes.push(...flatMap(D2Categories, (l) => _.map(l, (v) => v.toLowerCase())));
    stats.push(...['rpm']);
  }

  /**
   * Filter translation sets. Left-hand is the filter to run from filterFns, right side are possible filterResult
   * values that will set the left-hand to the "match."
   */
  const filterTrans = {
    dmg: ['arc', 'solar', 'void', 'kinetic'],
    type: itemTypes,
    tier: ['common', 'uncommon', 'rare', 'legendary', 'exotic', 'white', 'green', 'blue', 'purple', 'yellow'],
    classType: ['titan', 'hunter', 'warlock'],
    dupe: ['dupe', 'duplicate', 'dupelower'],
    tracked: ['tracked'],
    untracked: ['untracked'],
    locked: ['locked'],
    unlocked: ['unlocked'],
    stackable: ['stackable'],
    category: _.keys(categoryFilters),
    inloadout: ['inloadout'],
    new: ['new'],
    hasLight: ['light', 'haslight'],
    level: ['level'],
    weapon: ['weapon'],
    armor: ['armor'],
    equipment: ['equipment', 'equippable'],
    postmaster: ['postmaster', 'inpostmaster'],
    equipped: ['equipped'],
    transferable: ['transferable', 'movable'],
    infusable: ['infusable', 'infuse']
  };

  if (dimSettingsService.destinyVersion === 1) {
    Object.assign(filterTrans, {
      sublime: ['sublime'],
      incomplete: ['incomplete'],
      complete: ['complete'],
      xpcomplete: ['xpcomplete'],
      xpincomplete: ['xpincomplete', 'needsxp'],
      upgraded: ['upgraded'],
      unascended: ['unascended', 'unassended', 'unasscended'],
      ascended: ['ascended', 'assended', 'asscended'],
      reforgeable: ['reforgeable', 'reforge', 'rerollable', 'reroll'],
      ornament: ['ornament', 'ornamentmissing', 'ornamentunlocked'],
      engram: ['engram'],
      stattype: ['intellect', 'discipline', 'strength'],
      glimmer: ['glimmeritem', 'glimmerboost', 'glimmersupply'],
      year: ['year1', 'year2', 'year3'],
      vendor: ['fwc', 'do', 'nm', 'speaker', 'variks', 'shipwright', 'vanguard', 'osiris', 'xur', 'shaxx', 'cq', 'eris', 'ev', 'gunsmith'],
      activity: ['vanilla', 'trials', 'ib', 'qw', 'cd', 'srl', 'vog', 'ce', 'ttk', 'kf', 'roi', 'wotm', 'poe', 'coe', 'af', 'dawning', 'aot'],
      cosmetic: ['cosmetic']
    });
  } else {
    Object.assign(filterTrans, {
      powermod: ['powermod', 'haspowermod']
    });
  }

  if ($featureFlags.reviewsEnabled) {
    filterTrans.hasRating = ['rated', 'hasrating'];
  }

  const keywords = _.flatten(_.flatten(_.values(filterTrans)).map((word) => {
    return [`is:${word}`, `not:${word}`];
  }));

  if ($featureFlags.tagsEnabled) {
    dimSettingsService.itemTags.forEach((tag) => {
      if (tag.type) {
        keywords.push(`tag:${tag.type}`);
      } else {
        keywords.push("tag:none");
      }
    });
  }

  // Filters that operate on ranges (>, <, >=, <=)
  const comparisons = [":<", ":>", ":<=", ":>=", ":"];

  stats.forEach((word) => {
    const filter = `stat:${word}`;
    comparisons.forEach((comparison) => {
      keywords.push(filter + comparison);
    });
  });

  const ranges = ['light', 'level', 'stack', 'quality', 'percentage'];

  if ($featureFlags.reviewsEnabled) {
    ranges.push('rating');
    ranges.push('ratingcount');
  }

  ranges.forEach((range) => {
    comparisons.forEach((comparison) => {
      keywords.push(range + comparison);
    });
  });

  // free form notes on items
  if ($featureFlags.tagsEnabled) {
    keywords.push('notes:');
  }

  return {
    query: '',
    filterTrans: filterTrans,
    keywords: keywords,
    categoryFilters: categoryFilters
  };
}


function SearchFilter(dimSearchService) {
  return {
    controller: SearchFilterCtrl,
    controllerAs: 'vm',
    link: function link(scope, element) {
      const editor = new Textarea(element[0].getElementsByTagName('input')[0]);
      const textcomplete = new Textcomplete(editor);
      textcomplete.register([
        {
          words: dimSearchService.keywords,
          match: /\b((li|le|qu|pe|ra|is:|not:|tag:|notes:|stat:)\w*)$/i,
          search: function(term, callback) {
            callback(this.words.filter((word) => word.startsWith(term.toLowerCase())));
          },
          index: 1,
          replace: function(word) {
            word = word.toLowerCase();
            return (word.startsWith('is:') && word.startsWith('not:'))
              ? `${word} ` : word;
          }
        }<commit_msg>Update dimSearchFilter.directive.js<commit_after>import angular from 'angular';
import _ from 'underscore';
import template from './dimSearchFilter.directive.html';
import Textcomplete from 'textcomplete/lib/textcomplete';
import Textarea from 'textcomplete/lib/textarea';
import { flatMap } from '../util';

angular.module('dimApp')
  .factory('dimSearchService', SearchService)
  .directive('dimSearchFilter', SearchFilter);

function SearchService(dimSettingsService, dimCategory, D2Categories) {
  const categoryFilters = {
    pulserifle: ['CATEGORY_PULSE_RIFLE'],
    scoutrifle: ['CATEGORY_SCOUT_RIFLE'],
    handcannon: ['CATEGORY_HAND_CANNON'],
    autorifle: ['CATEGORY_AUTO_RIFLE'],
    sniperrifle: ['CATEGORY_SNIPER_RIFLE'],
    shotgun: ['CATEGORY_SHOTGUN'],
    sidearm: ['CATEGORY_SIDEARM'],
    rocketlauncher: ['CATEGORY_ROCKET_LAUNCHER'],
    fusionrifle: ['CATEGORY_FUSION_RIFLE'],
    sword: ['CATEGORY_SWORD'],
  };

  const itemTypes = [];

  const stats = ['charge', 'impact', 'range', 'stability', 'reload', 'magazine', 'aimassist', 'equipspeed'];

  // don't have access to dimSettingService yet here.
  if (dimSettingsService.destinyVersion === 1) {
    Object.assign(categoryFilters, {
      primaryweaponengram: ['CATEGORY_PRIMARY_WEAPON', 'CATEGORY_ENGRAM'],
      specialweaponengram: ['CATEGORY_SPECIAL_WEAPON', 'CATEGORY_ENGRAM'],
      heavyweaponengram: ['CATEGORY_HEAVY_WEAPON', 'CATEGORY_ENGRAM'],
      machinegun: ['CATEGORY_MACHINE_GUN'],
    });
    itemTypes.push(...flatMap(dimCategory, (l) => _.map(l, (v) => v.toLowerCase())));
    stats.push(...['rof']);
  } else {
    Object.assign(categoryFilters, {
      grenadelauncher: ['CATEGORY_GRENADE_LAUNCHER'],
      submachine: ['CATEGORY_SUBMACHINEGUN'],
    });
    itemTypes.push(...flatMap(D2Categories, (l) => _.map(l, (v) => v.toLowerCase())));
    stats.push(...['rpm']);
  }

  /**
   * Filter translation sets. Left-hand is the filter to run from filterFns, right side are possible filterResult
   * values that will set the left-hand to the "match."
   */
  const filterTrans = {
    dmg: ['arc', 'solar', 'void', 'kinetic'],
    type: itemTypes,
    tier: ['common', 'uncommon', 'rare', 'legendary', 'exotic', 'white', 'green', 'blue', 'purple', 'yellow'],
    classType: ['titan', 'hunter', 'warlock'],
    dupe: ['dupe', 'duplicate', 'dupelower'],
    tracked: ['tracked'],
    untracked: ['untracked'],
    locked: ['locked'],
    unlocked: ['unlocked'],
    stackable: ['stackable'],
    category: _.keys(categoryFilters),
    inloadout: ['inloadout'],
    new: ['new'],
    hasLight: ['light', 'haslight'],
    level: ['level'],
    weapon: ['weapon'],
    armor: ['armor'],
    equipment: ['equipment', 'equippable'],
    postmaster: ['postmaster', 'inpostmaster'],
    equipped: ['equipped'],
    transferable: ['transferable', 'movable'],
    infusable: ['infusable', 'infuse']
  };

  if (dimSettingsService.destinyVersion === 1) {
    Object.assign(filterTrans, {
      sublime: ['sublime'],
      incomplete: ['incomplete'],
      complete: ['complete'],
      xpcomplete: ['xpcomplete'],
      xpincomplete: ['xpincomplete', 'needsxp'],
      upgraded: ['upgraded'],
      unascended: ['unascended', 'unassended', 'unasscended'],
      ascended: ['ascended', 'assended', 'asscended'],
      reforgeable: ['reforgeable', 'reforge', 'rerollable', 'reroll'],
      ornament: ['ornament', 'ornamentmissing', 'ornamentunlocked'],
      engram: ['engram'],
      stattype: ['intellect', 'discipline', 'strength'],
      glimmer: ['glimmeritem', 'glimmerboost', 'glimmersupply'],
      year: ['year1', 'year2', 'year3'],
      vendor: ['fwc', 'do', 'nm', 'speaker', 'variks', 'shipwright', 'vanguard', 'osiris', 'xur', 'shaxx', 'cq', 'eris', 'ev', 'gunsmith'],
      activity: ['vanilla', 'trials', 'ib', 'qw', 'cd', 'srl', 'vog', 'ce', 'ttk', 'kf', 'roi', 'wotm', 'poe', 'coe', 'af', 'dawning', 'aot'],
      cosmetic: ['cosmetic']
    });
  } else {
    Object.assign(filterTrans, {
      powermod: ['powermod', 'haspowermod']
    });
  }

  if ($featureFlags.reviewsEnabled) {
    filterTrans.hasRating = ['rated', 'hasrating'];
  }

  const keywords = _.flatten(_.flatten(_.values(filterTrans)).map((word) => {
    return [`is:${word}`, `not:${word}`];
  }));

  if ($featureFlags.tagsEnabled) {
    dimSettingsService.itemTags.forEach((tag) => {
      if (tag.type) {
        keywords.push(`tag:${tag.type}`);
      } else {
        keywords.push("tag:none");
      }
    });
  }

  // Filters that operate on ranges (>, <, >=, <=)
  const comparisons = [":<", ":>", ":<=", ":>=", ":"];

  stats.forEach((word) => {
    const filter = `stat:${word}`;
    comparisons.forEach((comparison) => {
      keywords.push(filter + comparison);
    });
  });

  const ranges = ['light', 'level', 'stack', 'quality', 'percentage'];

  if ($featureFlags.reviewsEnabled) {
    ranges.push('rating');
    ranges.push('ratingcount');
  }

  ranges.forEach((range) => {
    comparisons.forEach((comparison) => {
      keywords.push(range + comparison);
    });
  });

  // free form notes on items
  if ($featureFlags.tagsEnabled) {
    keywords.push('notes:');
  }

  return {
    query: '',
    filterTrans: filterTrans,
    keywords: keywords,
    categoryFilters: categoryFilters
  };
}


function SearchFilter(dimSearchService) {
  return {
    controller: SearchFilterCtrl,
    controllerAs: 'vm',
    link: function link(scope, element) {
      const editor = new Textarea(element[0].getElementsByTagName('input')[0]);
      const textcomplete = new Textcomplete(editor);
      textcomplete.register([
        {
          words: dimSearchService.keywords,
          match: /\b((li|le|qu|pe|ra|is:|not:|tag:|notes:|sta)\w*)$/i,
          search: function(term, callback) {
            callback(this.words.filter((word) => word.startsWith(term.toLowerCase())));
          },
          index: 1,
          replace: function(word) {
            word = word.toLowerCase();
            return (word.startsWith('is:') && word.startsWith('not:'))
              ? `${word} ` : word;
          }
        }
{'fraction_non_alphanumeric': 0.18990147783251232, 'fraction_numerical': 0.0016420361247947454, 'mean_word_length': 3.7213178294573646, 'pattern_counts': {'":': 10, '<': 11, '<?xml version=': 0, '>': 33, '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': '516411', 'n_tokens_mistral': 4148, 'n_tokens_neox': 4132, 'n_words': 781}