/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ /** * Redux actions for the search state * @module actions/search */ import { isFulfilled } from "../utils/async-value"; import { getFirstSourceActorForGeneratedSource, getSourceList, getSettledSourceTextContent, isSourceBlackBoxed, getSearchOptions, } from "../selectors/index"; import { createLocation } from "../utils/location"; import { matchesGlobPatterns } from "../utils/source"; import { loadSourceText } from "./sources/loadSourceText"; import { searchKeys } from "../constants"; export function searchSources(query, onUpdatedResults, signal) { return async ({ dispatch, getState, searchWorker }) => { dispatch({ type: "SET_PROJECT_SEARCH_QUERY", query, }); const searchOptions = getSearchOptions( getState(), searchKeys.PROJECT_SEARCH ); const validSources = getSourceList(getState()).filter( source => !isSourceBlackBoxed(getState(), source) && !matchesGlobPatterns(source, searchOptions.excludePatterns) ); // Sort original entries first so that search results are more useful. // Deprioritize third-party scripts, so their results show last. validSources.sort((a, b) => { function isThirdParty(source) { return ( source?.url && (source.url.includes("node_modules") || source.url.includes("bower_components")) ); } if (a.isOriginal && !isThirdParty(a)) { return -1; } if (b.isOriginal && !isThirdParty(b)) { return 1; } if (!isThirdParty(a) && isThirdParty(b)) { return -1; } if (isThirdParty(a) && !isThirdParty(b)) { return 1; } return 0; }); const results = []; for (const source of validSources) { const sourceActor = getFirstSourceActorForGeneratedSource( getState(), source.id ); await dispatch(loadSourceText(source, sourceActor)); // This is the only asynchronous call in this method. // We may have stopped the search by closing the search panel or changing the query. // Avoid any further unecessary computation when the React Component tells us the query was cancelled. if (signal.aborted) { return; } const result = await searchSource(source, sourceActor, query, { getState, searchWorker, }); if (signal.aborted) { return; } if (result) { results.push(result); onUpdatedResults(results, false, signal); } } onUpdatedResults(results, true, signal); }; } export async function searchSource( source, sourceActor, query, { getState, searchWorker } ) { const state = getState(); const location = createLocation({ source, sourceActor, }); const content = getSettledSourceTextContent(state, location); let matches = []; if (content && isFulfilled(content) && content.value.type === "text") { const options = getSearchOptions(state, searchKeys.PROJECT_SEARCH); matches = await searchWorker.findSourceMatches( content.value, query, options ); } if (!matches.length) { return null; } return { type: "RESULT", location, // `matches` are generated by project-search worker's `findSourceMatches` method matches: matches.map(m => ({ type: "MATCH", location: createLocation({ ...location, // `matches` only contain line and column // `location` will already refer to the right source/sourceActor line: m.line, column: m.column, }), matchIndex: m.matchIndex, match: m.match, value: m.value, })), }; }