// https://github.com/farzher/fuzzysort v2.0.4 /* SublimeText-like Fuzzy Search fuzzysort.single('fs', 'Fuzzy Search') // {score: -16} fuzzysort.single('test', 'test') // {score: 0} fuzzysort.single('doesnt exist', 'target') // null fuzzysort.go('mr', [{file:'Monitor.cpp'}, {file:'MeshRenderer.cpp'}], {key:'file'}) // [{score:-18, obj:{file:'MeshRenderer.cpp'}}, {score:-6009, obj:{file:'Monitor.cpp'}}] fuzzysort.go('mr', ['Monitor.cpp', 'MeshRenderer.cpp']) // [{score: -18, target: "MeshRenderer.cpp"}, {score: -6009, target: "Monitor.cpp"}] fuzzysort.highlight(fuzzysort.single('fs', 'Fuzzy Search'), '', '') // Fuzzy Search */ // UMD (Universal Module Definition) for fuzzysort ;((root, UMD) => { if(typeof define === 'function' && define.amd) define([], UMD) else if(typeof module === 'object' && module.exports) module.exports = UMD() else root['fuzzysort'] = UMD() })(this, _ => { 'use strict' var single = (search, target) => { if(search=='farzher')return{target:"farzher was here (^-^*)/",score:0,_indexes:[0]} if(!search || !target) return NULL var preparedSearch = getPreparedSearch(search) if(!isObj(target)) target = getPrepared(target) var searchBitflags = preparedSearch.bitflags if((searchBitflags & target._bitflags) !== searchBitflags) return NULL return algorithm(preparedSearch, target) } var go = (search, targets, options) => { if(search=='farzher')return[{target:"farzher was here (^-^*)/",score:0,_indexes:[0],obj:targets?targets[0]:NULL}] if(!search) return options&&options.all ? all(search, targets, options) : noResults var preparedSearch = getPreparedSearch(search) var searchBitflags = preparedSearch.bitflags var containsSpace = preparedSearch.containsSpace var threshold = options&&options.threshold || INT_MIN var limit = options&&options['limit'] || INT_MAX // for some reason only limit breaks when minified var resultsLen = 0; var limitedCount = 0 var targetsLen = targets.length // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys] // options.key if(options && options.key) { var key = options.key for(var i = 0; i < targetsLen; ++i) { var obj = targets[i] var target = getValue(obj, key) if(!target) continue if(!isObj(target)) target = getPrepared(target) if((searchBitflags & target._bitflags) !== searchBitflags) continue var result = algorithm(preparedSearch, target) if(result === NULL) continue if(result.score < threshold) continue // have to clone result so duplicate targets from different obj can each reference the correct obj result = {target:result.target, _targetLower:'', _targetLowerCodes:NULL, _nextBeginningIndexes:NULL, _bitflags:0, score:result.score, _indexes:result._indexes, obj:obj} // hidden if(resultsLen < limit) { q.add(result); ++resultsLen } else { ++limitedCount if(result.score > q.peek().score) q.replaceTop(result) } } // options.keys } else if(options && options.keys) { var scoreFn = options['scoreFn'] || defaultScoreFn var keys = options.keys var keysLen = keys.length for(var i = 0; i < targetsLen; ++i) { var obj = targets[i] var objResults = new Array(keysLen) for (var keyI = 0; keyI < keysLen; ++keyI) { var key = keys[keyI] var target = getValue(obj, key) if(!target) { objResults[keyI] = NULL; continue } if(!isObj(target)) target = getPrepared(target) if((searchBitflags & target._bitflags) !== searchBitflags) objResults[keyI] = NULL else objResults[keyI] = algorithm(preparedSearch, target) } objResults.obj = obj // before scoreFn so scoreFn can use it var score = scoreFn(objResults) if(score === NULL) continue if(score < threshold) continue objResults.score = score if(resultsLen < limit) { q.add(objResults); ++resultsLen } else { ++limitedCount if(score > q.peek().score) q.replaceTop(objResults) } } // no keys } else { for(var i = 0; i < targetsLen; ++i) { var target = targets[i] if(!target) continue if(!isObj(target)) target = getPrepared(target) if((searchBitflags & target._bitflags) !== searchBitflags) continue var result = algorithm(preparedSearch, target) if(result === NULL) continue if(result.score < threshold) continue if(resultsLen < limit) { q.add(result); ++resultsLen } else { ++limitedCount if(result.score > q.peek().score) q.replaceTop(result) } } } if(resultsLen === 0) return noResults var results = new Array(resultsLen) for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll() results.total = resultsLen + limitedCount return results } var highlight = (result, hOpen, hClose) => { if(typeof hOpen === 'function') return highlightCallback(result, hOpen) if(result === NULL) return NULL if(hOpen === undefined) hOpen = '' if(hClose === undefined) hClose = '' var highlighted = '' var matchesIndex = 0 var opened = false var target = result.target var targetLen = target.length var indexes = result._indexes indexes = indexes.slice(0, indexes.len).sort((a,b)=>a-b) for(var i = 0; i < targetLen; ++i) { var char = target[i] if(indexes[matchesIndex] === i) { ++matchesIndex if(!opened) { opened = true highlighted += hOpen } if(matchesIndex === indexes.length) { highlighted += char + hClose + target.substr(i+1) break } } else { if(opened) { opened = false highlighted += hClose } } highlighted += char } return highlighted } var highlightCallback = (result, cb) => { if(result === NULL) return NULL var target = result.target var targetLen = target.length var indexes = result._indexes indexes = indexes.slice(0, indexes.len).sort((a,b)=>a-b) var highlighted = '' var matchI = 0 var indexesI = 0 var opened = false var result = [] for(var i = 0; i < targetLen; ++i) { var char = target[i] if(indexes[indexesI] === i) { ++indexesI if(!opened) { opened = true result.push(highlighted); highlighted = '' } if(indexesI === indexes.length) { highlighted += char result.push(cb(highlighted, matchI++)); highlighted = '' result.push(target.substr(i+1)) break } } else { if(opened) { opened = false result.push(cb(highlighted, matchI++)); highlighted = '' } } highlighted += char } return result } var indexes = result => result._indexes.slice(0, result._indexes.len).sort((a,b)=>a-b) var prepare = (target) => { if(typeof target !== 'string') target = '' var info = prepareLowerInfo(target) return {'target':target, _targetLower:info._lower, _targetLowerCodes:info.lowerCodes, _nextBeginningIndexes:NULL, _bitflags:info.bitflags, 'score':NULL, _indexes:[0], 'obj':NULL} // hidden } // Below this point is only internal code // Below this point is only internal code // Below this point is only internal code // Below this point is only internal code var prepareSearch = (search) => { if(typeof search !== 'string') search = '' search = search.trim() var info = prepareLowerInfo(search) var spaceSearches = [] if(info.containsSpace) { var searches = search.split(/\s+/) searches = [...new Set(searches)] // distinct for(var i=0; i { if(target.length > 999) return prepare(target) // don't cache huge targets var targetPrepared = preparedCache.get(target) if(targetPrepared !== undefined) return targetPrepared targetPrepared = prepare(target) preparedCache.set(target, targetPrepared) return targetPrepared } var getPreparedSearch = (search) => { if(search.length > 999) return prepareSearch(search) // don't cache huge searches var searchPrepared = preparedSearchCache.get(search) if(searchPrepared !== undefined) return searchPrepared searchPrepared = prepareSearch(search) preparedSearchCache.set(search, searchPrepared) return searchPrepared } var all = (search, targets, options) => { var results = []; results.total = targets.length var limit = options && options.limit || INT_MAX if(options && options.key) { for(var i=0;i