| /* | |
| * Copyright (C) 2013 Google Inc. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are | |
| * met: | |
| * | |
| * * Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * * Redistributions in binary form must reproduce the above | |
| * copyright notice, this list of conditions and the following disclaimer | |
| * in the documentation and/or other materials provided with the | |
| * distribution. | |
| * * Neither the name of Google Inc. nor the names of its | |
| * contributors may be used to endorse or promote products derived from | |
| * this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| /** | |
| * @unrestricted | |
| */ | |
| Sources.FilePathScoreFunction = class { | |
| /** | |
| * @param {string} query | |
| */ | |
| constructor(query) { | |
| this._query = query; | |
| this._queryUpperCase = query.toUpperCase(); | |
| this._score = new Int32Array(20 * 100); | |
| this._sequence = new Int32Array(20 * 100); | |
| this._dataUpperCase = ''; | |
| this._fileNameIndex = 0; | |
| } | |
| /** | |
| * @param {string} data | |
| * @param {?Array<number>} matchIndexes | |
| * @return {number} | |
| */ | |
| score(data, matchIndexes) { | |
| if (!data || !this._query) { | |
| return 0; | |
| } | |
| const n = this._query.length; | |
| const m = data.length; | |
| if (!this._score || this._score.length < n * m) { | |
| this._score = new Int32Array(n * m * 2); | |
| this._sequence = new Int32Array(n * m * 2); | |
| } | |
| const score = this._score; | |
| const sequence = /** @type {!Int32Array} */ (this._sequence); | |
| this._dataUpperCase = data.toUpperCase(); | |
| this._fileNameIndex = data.lastIndexOf('/'); | |
| for (let i = 0; i < n; ++i) { | |
| for (let j = 0; j < m; ++j) { | |
| const skipCharScore = j === 0 ? 0 : score[i * m + j - 1]; | |
| const prevCharScore = i === 0 || j === 0 ? 0 : score[(i - 1) * m + j - 1]; | |
| const consecutiveMatch = i === 0 || j === 0 ? 0 : sequence[(i - 1) * m + j - 1]; | |
| const pickCharScore = this._match(this._query, data, i, j, consecutiveMatch); | |
| if (pickCharScore && prevCharScore + pickCharScore >= skipCharScore) { | |
| sequence[i * m + j] = consecutiveMatch + 1; | |
| score[i * m + j] = (prevCharScore + pickCharScore); | |
| } else { | |
| sequence[i * m + j] = 0; | |
| score[i * m + j] = skipCharScore; | |
| } | |
| } | |
| } | |
| if (matchIndexes) { | |
| this._restoreMatchIndexes(sequence, n, m, matchIndexes); | |
| } | |
| const maxDataLength = 256; | |
| return score[n * m - 1] * maxDataLength + (maxDataLength - data.length); | |
| } | |
| /** | |
| * @param {string} data | |
| * @param {number} j | |
| * @return {boolean} | |
| */ | |
| _testWordStart(data, j) { | |
| if (j === 0) { | |
| return true; | |
| } | |
| const prevChar = data.charAt(j - 1); | |
| return prevChar === '_' || prevChar === '-' || prevChar === '/' || | |
| (data[j - 1] !== this._dataUpperCase[j - 1] && data[j] === this._dataUpperCase[j]); | |
| } | |
| /** | |
| * @param {!Int32Array} sequence | |
| * @param {number} n | |
| * @param {number} m | |
| * @param {!Array<number>} out | |
| */ | |
| _restoreMatchIndexes(sequence, n, m, out) { | |
| let i = n - 1, j = m - 1; | |
| while (i >= 0 && j >= 0) { | |
| switch (sequence[i * m + j]) { | |
| case 0: | |
| --j; | |
| break; | |
| default: | |
| out.push(j); | |
| --i; | |
| --j; | |
| break; | |
| } | |
| } | |
| out.reverse(); | |
| } | |
| /** | |
| * @param {string} query | |
| * @param {string} data | |
| * @param {number} i | |
| * @param {number} j | |
| * @return {number} | |
| */ | |
| _singleCharScore(query, data, i, j) { | |
| const isWordStart = this._testWordStart(data, j); | |
| const isFileName = j > this._fileNameIndex; | |
| const isPathTokenStart = j === 0 || data[j - 1] === '/'; | |
| const isCapsMatch = query[i] === data[j] && query[i] === this._queryUpperCase[i]; | |
| let score = 10; | |
| if (isPathTokenStart) { | |
| score += 4; | |
| } | |
| if (isWordStart) { | |
| score += 2; | |
| } | |
| if (isCapsMatch) { | |
| score += 6; | |
| } | |
| if (isFileName) { | |
| score += 4; | |
| } | |
| // promote the case of making the whole match in the filename | |
| if (j === this._fileNameIndex + 1 && i === 0) { | |
| score += 5; | |
| } | |
| if (isFileName && isWordStart) { | |
| score += 3; | |
| } | |
| return score; | |
| } | |
| /** | |
| * @param {string} query | |
| * @param {string} data | |
| * @param {number} i | |
| * @param {number} j | |
| * @param {number} sequenceLength | |
| * @return {number} | |
| */ | |
| _sequenceCharScore(query, data, i, j, sequenceLength) { | |
| const isFileName = j > this._fileNameIndex; | |
| const isPathTokenStart = j === 0 || data[j - 1] === '/'; | |
| let score = 10; | |
| if (isFileName) { | |
| score += 4; | |
| } | |
| if (isPathTokenStart) { | |
| score += 5; | |
| } | |
| score += sequenceLength * 4; | |
| return score; | |
| } | |
| /** | |
| * @param {string} query | |
| * @param {string} data | |
| * @param {number} i | |
| * @param {number} j | |
| * @param {number} consecutiveMatch | |
| * @return {number} | |
| */ | |
| _match(query, data, i, j, consecutiveMatch) { | |
| if (this._queryUpperCase[i] !== this._dataUpperCase[j]) { | |
| return 0; | |
| } | |
| if (!consecutiveMatch) { | |
| return this._singleCharScore(query, data, i, j); | |
| } else { | |
| return this._sequenceCharScore(query, data, i, j - consecutiveMatch, consecutiveMatch); | |
| } | |
| } | |
| }; |