UNPKG

monaco-editor-core

Version:

A browser based code editor

139 lines (138 loc) 5.71 kB
import { createMatches as createFuzzyMatches, fuzzyScore } from './filters.js'; import { sep } from './path.js'; import { isWindows } from './platform.js'; import { stripWildcards } from './strings.js'; const NO_SCORE2 = [undefined, []]; export function scoreFuzzy2(target, query, patternStart = 0, wordStart = 0) { // Score: multiple inputs const preparedQuery = query; if (preparedQuery.values && preparedQuery.values.length > 1) { return doScoreFuzzy2Multiple(target, preparedQuery.values, patternStart, wordStart); } // Score: single input return doScoreFuzzy2Single(target, query, patternStart, wordStart); } function doScoreFuzzy2Multiple(target, query, patternStart, wordStart) { let totalScore = 0; const totalMatches = []; for (const queryPiece of query) { const [score, matches] = doScoreFuzzy2Single(target, queryPiece, patternStart, wordStart); if (typeof score !== 'number') { // if a single query value does not match, return with // no score entirely, we require all queries to match return NO_SCORE2; } totalScore += score; totalMatches.push(...matches); } // if we have a score, ensure that the positions are // sorted in ascending order and distinct return [totalScore, normalizeMatches(totalMatches)]; } function doScoreFuzzy2Single(target, query, patternStart, wordStart) { const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), wordStart, { firstMatchCanBeWeak: true, boostFullMatch: true }); if (!score) { return NO_SCORE2; } return [score[0], createFuzzyMatches(score)]; } const NO_ITEM_SCORE = Object.freeze({ score: 0 }); function normalizeMatches(matches) { // sort matches by start to be able to normalize const sortedMatches = matches.sort((matchA, matchB) => { return matchA.start - matchB.start; }); // merge matches that overlap const normalizedMatches = []; let currentMatch = undefined; for (const match of sortedMatches) { // if we have no current match or the matches // do not overlap, we take it as is and remember // it for future merging if (!currentMatch || !matchOverlaps(currentMatch, match)) { currentMatch = match; normalizedMatches.push(match); } // otherwise we merge the matches else { currentMatch.start = Math.min(currentMatch.start, match.start); currentMatch.end = Math.max(currentMatch.end, match.end); } } return normalizedMatches; } function matchOverlaps(matchA, matchB) { if (matchA.end < matchB.start) { return false; // A ends before B starts } if (matchB.end < matchA.start) { return false; // B ends before A starts } return true; } /* * If a query is wrapped in quotes, the user does not want to * use fuzzy search for this query. */ function queryExpectsExactMatch(query) { return query.startsWith('"') && query.endsWith('"'); } /** * Helper function to prepare a search value for scoring by removing unwanted characters * and allowing to score on multiple pieces separated by whitespace character. */ const MULTIPLE_QUERY_VALUES_SEPARATOR = ' '; export function prepareQuery(original) { if (typeof original !== 'string') { original = ''; } const originalLowercase = original.toLowerCase(); const { pathNormalized, normalized, normalizedLowercase } = normalizeQuery(original); const containsPathSeparator = pathNormalized.indexOf(sep) >= 0; const expectExactMatch = queryExpectsExactMatch(original); let values = undefined; const originalSplit = original.split(MULTIPLE_QUERY_VALUES_SEPARATOR); if (originalSplit.length > 1) { for (const originalPiece of originalSplit) { const expectExactMatchPiece = queryExpectsExactMatch(originalPiece); const { pathNormalized: pathNormalizedPiece, normalized: normalizedPiece, normalizedLowercase: normalizedLowercasePiece } = normalizeQuery(originalPiece); if (normalizedPiece) { if (!values) { values = []; } values.push({ original: originalPiece, originalLowercase: originalPiece.toLowerCase(), pathNormalized: pathNormalizedPiece, normalized: normalizedPiece, normalizedLowercase: normalizedLowercasePiece, expectContiguousMatch: expectExactMatchPiece }); } } } return { original, originalLowercase, pathNormalized, normalized, normalizedLowercase, values, containsPathSeparator, expectContiguousMatch: expectExactMatch }; } function normalizeQuery(original) { let pathNormalized; if (isWindows) { pathNormalized = original.replace(/\//g, sep); // Help Windows users to search for paths when using slash } else { pathNormalized = original.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash } // we remove quotes here because quotes are used for exact match search const normalized = stripWildcards(pathNormalized).replace(/\s|"/g, ''); return { pathNormalized, normalized, normalizedLowercase: normalized.toLowerCase() }; } export function pieceToQuery(arg1) { if (Array.isArray(arg1)) { return prepareQuery(arg1.map(piece => piece.original).join(MULTIPLE_QUERY_VALUES_SEPARATOR)); } return prepareQuery(arg1.original); } //#endregion