// SPDX-FileCopyrightText: 2014 John Hawthorn // // SPDX-License-Identifier: MIT window.fzy = (function() { var SCORE_MIN = -Infinity; var SCORE_MAX = Infinity; var SCORE_GAP_LEADING = -0.005 var SCORE_GAP_TRAILING = -0.005 var SCORE_GAP_INNER = -0.01 var SCORE_MATCH_CONSECUTIVE = 1.0 var SCORE_MATCH_SLASH = 0.9 var SCORE_MATCH_WORD = 0.8 var SCORE_MATCH_CAPITAL = 0.7 var SCORE_MATCH_DOT = 0.6 function islower(s) { return s.toLowerCase() === s; } function isupper(s) { return s.toUpperCase() === s; } function precompute_bonus(haystack) { /* Which positions are beginning of words */ var m = haystack.length; var match_bonus = new Array(m); var last_ch = '/'; for (var i = 0; i < m; i++) { var ch = haystack[i]; if (last_ch === '/') { match_bonus[i] = SCORE_MATCH_SLASH; } else if (last_ch === '-' || last_ch === '_' || last_ch === ' ') { match_bonus[i] = SCORE_MATCH_WORD; } else if (last_ch === '.') { match_bonus[i] = SCORE_MATCH_DOT; } else if (islower(last_ch) && isupper(ch)) { match_bonus[i] = SCORE_MATCH_CAPITAL; } else { match_bonus[i] = 0; } last_ch = ch; } return match_bonus; } function compute(needle, haystack, D, M) { var n = needle.length; var m = haystack.length; var match_bonus = precompute_bonus(haystack, match_bonus); /* * D[][] Stores the best score for this position ending with a match. * M[][] Stores the best possible score at this position. */ for (var i = 0; i < n; i++) { D[i] = new Array(m); M[i] = new Array(m); var prev_score = SCORE_MIN; var gap_score = i === n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER; for (var j = 0; j < m; j++) { if (needle[i] === haystack[j]) { var score = SCORE_MIN; if (!i) { score = (j * SCORE_GAP_LEADING) + match_bonus[j]; } else if (j) { /* i > 0 && j > 0*/ score = Math.max( M[i - 1][j - 1] + match_bonus[j], /* consecutive match, doesn't stack with match_bonus */ D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE); } D[i][j] = score; M[i][j] = prev_score = Math.max(score, prev_score + gap_score); } else { D[i][j] = SCORE_MIN; M[i][j] = prev_score = prev_score + gap_score; } } } } function score(needle, haystack) { var n = needle.length; var m = haystack.length; if (!n || !m) return SCORE_MIN; if (n === m) { /* Since this method can only be called with a haystack which * matches needle. If the lengths of the strings are equal the * strings themselves must also be equal (ignoring case). */ return SCORE_MAX; } if (m > 1024) { /* * Unreasonably large candidate: return no score * If it is a valid match it will still be returned, it will * just be ranked below any reasonably sized candidates */ return SCORE_MIN; } var D = new Array(n); var M = new Array(n); compute(needle, haystack, D, M) return M[n - 1][m - 1]; } function positions(needle, haystack) { var n = needle.length; var m = haystack.length; var positions = new Array(n); if (!n || !m) return positions; if (n === m) { for (var i = 0; i < n; i++) positions[i] = i; return positions; } if (m > 1024) { return positions; } var D = new Array(n); var M = new Array(n); compute(needle, haystack, D, M) /* backtrack to find the positions of optimal matching */ var match_required = false; for (var i = n - 1, j = m - 1; i >= 0; i--) { for (; j >= 0; j--) { /* * There may be multiple paths which result in * the optimal weight. * * For simplicity, we will pick the first one * we encounter, the latest in the candidate * string. */ if (D[i][j] !== SCORE_MIN && (match_required || D[i][j] === M[i][j])) { /* If this score was determined using * SCORE_MATCH_CONSECUTIVE, the * previous character MUST be a match */ match_required = i && j && M[i][j] === D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE; positions[i] = j--; break; } } } return positions; } function hasMatch(needle, haystack) { var l = needle.length for (var i = 0, j = 0; i < l; i += 1) { j = haystack.indexOf(needle[i], j) + 1 if (j === 0) return false } return true } function filter(needle, items, selector) { const isCaseSensitive = needle.toLowerCase() !== needle; const actualNeedle = isCaseSensitive ? needle : needle.toLowerCase(); const filteredItems = items.reduce((results, item) => { const haystack = isCaseSensitive ? selector(item) : selector(item).toLowerCase(); if (hasMatch(actualNeedle, haystack)) results.push({ item, score: score(needle, haystack) }) return results; }, []) filteredItems.sort((a, b) => b.score - a.score) return filteredItems; } return { /* constants */ SCORE_MIN, SCORE_MAX, SCORE_GAP_LEADING, SCORE_GAP_TRAILING, SCORE_GAP_INNER, SCORE_MATCH_CONSECUTIVE, SCORE_MATCH_SLASH, SCORE_MATCH_WORD, SCORE_MATCH_CAPITAL, SCORE_MATCH_DOT, /* functions */ score, positions, hasMatch, filter, } })();