diff --git a/AUTHORS.rst b/AUTHORS.rst index 1b4d6e7c85b..bad6231382d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -64,6 +64,7 @@ Contributors * Ɓukasz Langa -- partial support for autodoc * Marco Buttu -- doctest extension (pyversion option) * Martin Hans -- autodoc improvements +* Marvin Herbold -- HTML exact phrase search support * Martin Larralde -- additional napoleon admonitions * Martin Mahner -- nature theme * Matthew Fernandez -- todo extension fix diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index b08d58c9b9b..654219bcefc 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -57,7 +57,7 @@ const _removeChildren = (element) => { const _escapeRegExp = (string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string -const _displayItem = (item, searchTerms, highlightTerms) => { +const _displayItem = (item, searchTerms, highlightTerms, exactSearchPhrases) => { const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; @@ -97,6 +97,14 @@ const _displayItem = (item, searchTerms, highlightTerms) => { fetch(requestUrl) .then((responseData) => responseData.text()) .then((data) => { + + // exclude results that don't contain exact phrases if we are searching for them + if (data) { + const lowercaseData = data.toLowerCase(); + const mismatch = (s) => !lowercaseData.includes(s); + if (exactSearchPhrases.some(mismatch)) return; + } + if (data) listItem.appendChild( Search.makeSearchSummary(data, searchTerms, anchor) @@ -124,13 +132,14 @@ const _displayNextItem = ( resultCount, searchTerms, highlightTerms, + exactSearchPhrases, ) => { // results left, load the summary and display it // this is intended to be dynamic (don't sub resultsCount) if (results.length) { - _displayItem(results.pop(), searchTerms, highlightTerms); + _displayItem(results.pop(), searchTerms, highlightTerms, exactSearchPhrases); setTimeout( - () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms, exactSearchPhrases), 5 ); } @@ -275,6 +284,15 @@ const Search = { const excludedTerms = new Set(); const highlightTerms = new Set(); const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + + // prepare the exact phrase search feature + const exactSearchPhrases = []; + const exactSearchPattern = /"([^"]+?)"/g; + let match; + while (match = exactSearchPattern.exec(query)) { + exactSearchPhrases.push(match[1].toLowerCase()); + } + splitQuery(query.trim()).forEach((queryTerm) => { const queryTermLower = queryTerm.toLowerCase(); @@ -304,7 +322,7 @@ const Search = { // console.info("required: ", [...searchTerms]); // console.info("excluded: ", [...excludedTerms]); - return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms, exactSearchPhrases]; }, /** @@ -403,7 +421,7 @@ const Search = { }, query: (query) => { - const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms, exactSearchPhrases] = Search._parseQuery(query); const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); // for debugging @@ -411,7 +429,7 @@ const Search = { // console.info("search results:", Search.lastresults); // print the results - _displayNextItem(results, results.length, searchTerms, highlightTerms); + _displayNextItem(results, results.length, searchTerms, highlightTerms, exactSearchPhrases); }, /**