# all files / -private/ indices-implementation.js

100% Statements 33/33
100% Branches 8/8
100% Functions 4/4
100% Lines 33/33

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89

    1×
    24×

    24×
    7×

    17×
    17×
    17×

    17×
    17×
    17×
    22×

    22×
    22×

    22×

    17×
    6×

    17×

    1×
    22×

    1×
    17×
    17×

    1×
    24×

    24×
    24×

    24×
    12×
    12×

    24×
    22×
    22×

    24×

     'use strict';

    import Ember from 'ember';
    import _lang from 'lodash/lang';

    /**
     * Indices Implementation
     *
     * Highlight `value` input String with String.indexOf() implementation that can be more performant on many short
     * Strings than the approach with a Regular Expression.
     *
     * @param {String} value The template string to highlight matches if any
     * @param {String} query The string to search in `value`
     * @param {Object} options
     * @param {Boolean} options.caseSensitive
     *
     * @returns {Ember.String.htmlSafe}
     */
    export default function (value, query, options) {
      const indices = findIndicesOf(query, value, options.caseSensitive);

      // If we couldn't find any match, return input untouched
      if (_lang.isEmpty(indices)) {
        return new Ember.String.htmlSafe(value);
      }

      const queryLength = query.length;
      const indicesCount = indices.length;
      const valueLength = value.length;

      let result = '';
      let lastIndex;
      for (let i = 0; i < indicesCount; i++) {
        const index = lastIndex = indices[i];

        // Find and add unmatched characters before this match
        const matchPrefixStartIndex = findMatchPrefixStartIndex(indices, i, queryLength);
        result += value.slice(matchPrefixStartIndex, index);

        // Add wrapped match
        result += `<span class="mark">${value.slice(index, index + queryLength)}</span>`;
      }

      // If applicable, add remaining characters after the last match
      if (hasRemainingUnmatchedCharacters(lastIndex, queryLength, valueLength)) {
        result += value.slice(lastIndex + queryLength, valueLength);
      }

      return new Ember.String.htmlSafe(result);
    }

    function findMatchPrefixStartIndex(indices, i, queryLength) {
      return i === 0 ? 0 : indices[i - 1] + queryLength;
    }

    function hasRemainingUnmatchedCharacters(lastIndex, queryLength, valueLength) {
      const lastMatchEndIndex = lastIndex + queryLength;
      return lastMatchEndIndex < valueLength - 1;
    }

    /**
     * Find and return all indices of string `query` in `source`.
     *
     * @param {String} query
     * @param {String} source
     * @param {Boolean} [caseSensitive=false]
     * @returns {Number[]}
     * @private
     */
    function findIndicesOf(query, source, caseSensitive) {
      let index, startIndex = 0;

      const queryLength = query.length;
      const indices = [];

      if (!caseSensitive) {
        query = query.toLowerCase();
        source = source.toLowerCase();
      }

      while ((index = source.indexOf(query, startIndex)) > -1) {
        indices.push(index);
        startIndex = index + queryLength;
      }

      return indices;
    }