Skip to main content
Module

x/fuse/src/search/extended/index.js

Lightweight fuzzy-search, in JavaScript
Latest
File
import parseQuery from './parseQuery'import FuzzyMatch from './FuzzyMatch'import IncludeMatch from './IncludeMatch'import Config from '../../core/config'
// These extended matchers can return an array of matches, as opposed// to a singl matchconst MultiMatchSet = new Set([FuzzyMatch.type, IncludeMatch.type])
/** * Command-like searching * ====================== * * Given multiple search terms delimited by spaces.e.g. `^jscript .python$ ruby !java`, * search in a given text. * * Search syntax: * * | Token | Match type | Description | * | ----------- | -------------------------- | -------------------------------------- | * | `jscript` | fuzzy-match | Items that fuzzy match `jscript` | * | `=scheme` | exact-match | Items that are `scheme` | * | `'python` | include-match | Items that include `python` | * | `!ruby` | inverse-exact-match | Items that do not include `ruby` | * | `^java` | prefix-exact-match | Items that start with `java` | * | `!^earlang` | inverse-prefix-exact-match | Items that do not start with `earlang` | * | `.js$` | suffix-exact-match | Items that end with `.js` | * | `!.go$` | inverse-suffix-exact-match | Items that do not end with `.go` | * * A single pipe character acts as an OR operator. For example, the following * query matches entries that start with `core` and end with either`go`, `rb`, * or`py`. * * ``` * ^core go$ | rb$ | py$ * ``` */export default class ExtendedSearch { constructor( pattern, { isCaseSensitive = Config.isCaseSensitive, includeMatches = Config.includeMatches, minMatchCharLength = Config.minMatchCharLength, findAllMatches = Config.findAllMatches, location = Config.location, threshold = Config.threshold, distance = Config.distance } = {} ) { this.query = null this.options = { isCaseSensitive, includeMatches, minMatchCharLength, findAllMatches, location, threshold, distance }
this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase() this.query = parseQuery(this.pattern, this.options) }
static condition(_, options) { return options.useExtendedSearch }
searchIn(text) { const query = this.query
if (!query) { return { isMatch: false, score: 1 } }
const { includeMatches, isCaseSensitive } = this.options
text = isCaseSensitive ? text : text.toLowerCase()
let numMatches = 0 let allIndices = [] let totalScore = 0
// ORs for (let i = 0, qLen = query.length; i < qLen; i += 1) { const searchers = query[i]
// Reset indices allIndices.length = 0 numMatches = 0
// ANDs for (let j = 0, pLen = searchers.length; j < pLen; j += 1) { const searcher = searchers[j] const { isMatch, indices, score } = searcher.search(text)
if (isMatch) { numMatches += 1 totalScore += score if (includeMatches) { const type = searcher.constructor.type if (MultiMatchSet.has(type)) { allIndices = [...allIndices, ...indices] } else { allIndices.push(indices) } } } else { totalScore = 0 numMatches = 0 allIndices.length = 0 break } }
// OR condition, so if TRUE, return if (numMatches) { let result = { isMatch: true, score: totalScore / numMatches }
if (includeMatches) { result.indices = allIndices }
return result } }
// Nothing was matched return { isMatch: false, score: 1 } }}