Source code

Revision control

Copy as Markdown

Other Tools

/* 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 <http://mozilla.org/MPL/2.0/>. */
/**
* 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,
})),
};
}