diff options
Diffstat (limited to 'devtools/client/debugger/src/actions/project-text-search.js')
-rw-r--r-- | devtools/client/debugger/src/actions/project-text-search.js | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/actions/project-text-search.js b/devtools/client/debugger/src/actions/project-text-search.js new file mode 100644 index 0000000000..26ea0df107 --- /dev/null +++ b/devtools/client/debugger/src/actions/project-text-search.js @@ -0,0 +1,171 @@ +/* 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"; +import { createLocation } from "../utils/location"; +import { matchesGlobPatterns } from "../utils/source"; +import { loadSourceText } from "./sources/loadSourceText"; +import { + getProjectSearchOperation, + getProjectSearchStatus, +} from "../selectors/project-text-search"; +import { statusType } from "../reducers/project-text-search"; +import { searchKeys } from "../constants"; + +export function addSearchQuery(cx, query) { + return { type: "ADD_QUERY", cx, query }; +} + +export function addOngoingSearch(cx, ongoingSearch) { + return { type: "ADD_ONGOING_SEARCH", cx, ongoingSearch }; +} + +export function addSearchResult(cx, location, matches) { + return { + type: "ADD_SEARCH_RESULT", + cx, + location, + matches, + }; +} + +export function clearSearchResults(cx) { + return { type: "CLEAR_SEARCH_RESULTS", cx }; +} + +export function clearSearch(cx) { + return { type: "CLEAR_SEARCH", cx }; +} + +export function updateSearchStatus(cx, status) { + return { type: "UPDATE_STATUS", cx, status }; +} + +export function closeProjectSearch(cx) { + return ({ dispatch, getState }) => { + dispatch(stopOngoingSearch(cx)); + dispatch({ type: "CLOSE_PROJECT_SEARCH" }); + }; +} + +export function stopOngoingSearch(cx) { + return ({ dispatch, getState }) => { + const state = getState(); + const ongoingSearch = getProjectSearchOperation(state); + const status = getProjectSearchStatus(state); + if (ongoingSearch && status !== statusType.done) { + ongoingSearch.cancel(); + dispatch(updateSearchStatus(cx, statusType.cancelled)); + } + }; +} + +export function searchSources(cx, query) { + let cancelled = false; + + const search = async ({ dispatch, getState }) => { + dispatch(stopOngoingSearch(cx)); + await dispatch(addOngoingSearch(cx, search)); + await dispatch(clearSearchResults(cx)); + await dispatch(addSearchQuery(cx, query)); + dispatch(updateSearchStatus(cx, statusType.fetching)); + 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; + }); + + for (const source of validSources) { + if (cancelled) { + return; + } + + const sourceActor = getFirstSourceActorForGeneratedSource( + getState(), + source.id + ); + await dispatch(loadSourceText(cx, source, sourceActor)); + await dispatch(searchSource(cx, source, sourceActor, query)); + } + dispatch(updateSearchStatus(cx, statusType.done)); + }; + + search.cancel = () => { + cancelled = true; + }; + + return search; +} + +export function searchSource(cx, source, sourceActor, query) { + return async ({ dispatch, getState, searchWorker }) => { + if (!source) { + return; + } + const state = getState(); + const location = createLocation({ + source, + sourceActor, + }); + + const options = getSearchOptions(state, searchKeys.PROJECT_SEARCH); + const content = getSettledSourceTextContent(state, location); + let matches = []; + + if (content && isFulfilled(content) && content.value.type === "text") { + matches = await searchWorker.findSourceMatches( + content.value, + query, + options + ); + } + if (!matches.length) { + return; + } + dispatch(addSearchResult(cx, location, matches)); + }; +} |