diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 4170c73b24625..500d2284e9fa6 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -1446,6 +1446,7 @@ fn init_id_map() -> FxHashMap, usize> { map.insert("not-displayed".into(), 1); map.insert("alternative-display".into(), 1); map.insert("search".into(), 1); + map.insert("search-helper".into(), 1); // This is the list of IDs used in HTML generated in Rust (including the ones // used in tera template files). map.insert("mainThemeStyle".into(), 1); diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index c6933a8254bc2..99634fdb0633a 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -946,7 +946,7 @@ table, font-weight: 500; margin-bottom: 20px; } -#crate-search { +#crate-search, #search-helper select { min-width: 115px; margin-top: 5px; padding-left: 0.3125em; @@ -971,7 +971,7 @@ table, .search-container { margin-top: 4px; } -.search-input { +.search-input, #search-helper input:not([type="checkbox"]), #search-helper button { /* Override Normalize.css: it has a rule that sets -webkit-appearance: textfield for search inputs. That causes rounded corners and no border on iOS Safari. */ @@ -990,6 +990,45 @@ table, width: 100%; } +#search-helper { + margin-top: 6px; +} +#search-helper summary { + font-size: 1.15rem; +} +#search-helper select { + padding-top: 4px; + padding-bottom: 4px; + margin-bottom: 5px; +} +#search-helper .end-buttons { + padding-top: 5px; +} +#search-helper select, #search-helper button, #search-helper summary { + cursor: pointer; +} +#search-helper > *:not(input) { + width: 100%; +} +#search-helper .search-buttons { + display: flex; +} +#search-helper .search-returned, #search-helper .search-arguments { + margin: 15px 0; +} +#search-helper .search-arguments > *, #search-helper .search-returned > *, #search-helper .search-buttons { + margin-top: 5px; +} +#search-helper .search-buttons button:not(:last-of-type) { + margin-right: 5px; +} +#search-helper .generics { + padding-left: 14px; +} +#search-helper .search-literal, #search-helper .search-literal > * { + cursor: pointer; +} + .search-results { display: none; padding-bottom: 2em; @@ -2110,7 +2149,7 @@ in storage.js plus the media query with (min-width: 701px) width: 50%; } - #crate-search { + #crate-search, #search-helper select { border-radius: 4px; } diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index 142ce456c5213..e1f0e3417eadf 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -182,14 +182,18 @@ details.rustdoc-toggle > summary::before { filter: invert(100%); } -#crate-search, .search-input { +#crate-search, .search-input, #search-helper input, #search-helper button, #search-helper select { background-color: #141920; +} + +#crate-search { /* Without the `!important`, the border-color is ignored for ``... */ border-color: #424c57 !important; } -.search-input { +.search-input, #search-helper input, #search-helper button, #search-helper select { color: #ffffff; + border-color: #424c57; } .module-item .stab, @@ -486,7 +490,8 @@ kbd { } #settings-menu > a:hover, #settings-menu > a:focus, -#help-button > button:hover, #help-button > button:focus { +#help-button > button:hover, #help-button > button:focus, +#search-helper button:focus, #search-helper button:hover { border-color: #e0e0e0; } diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index aeaca7515f962..22915c26ca850 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -156,18 +156,21 @@ details.rustdoc-toggle > summary::before { filter: invert(100%); } -#crate-search, .search-input { +#crate-search, .search-input, #search-helper input, #search-helper button, #search-helper select { color: #111; background-color: #f0f0f0; +} + +#crate-search { /* Without the `!important`, the border-color is ignored for ``... */ border-color: #f0f0f0 !important; } -.search-input { +.search-input, .search-input, #search-helper input, #search-helper button { border-color: #e0e0e0; } -.search-input:focus { +.search-input:focus, #search-helper input:focus { border-color: #008dfd; } @@ -325,7 +328,8 @@ kbd { } #settings-menu > a:hover, #settings-menu > a:focus, -#help-button > button:hover, #help-button > button:focus { +#help-button > button:hover, #help-button > button:focus, +#search-helper button:focus, #search-helper button:hover { border-color: #ffb900; } diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index 54d1a7b65d665..3e839e9a8d2f5 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -144,13 +144,16 @@ details.rustdoc-toggle > summary::before { color: #999; } -#crate-search, .search-input { +#crate-search, .search-input, #search-helper input, #search-helper button, #search-helper select { background-color: white; +} + +#crate-search { /* Without the `!important`, the border-color is ignored for ``... */ border-color: #e0e0e0 !important; } -.search-input:focus { +.search-input:focus, #search-helper input:focus { border-color: #66afe9; } @@ -308,7 +311,8 @@ kbd { } #settings-menu > a:hover, #settings-menu > a:focus, -#help-button > button:hover, #help-button > button:focus { +#help-button > button:hover, #help-button > button:focus, +#search-helper button:focus, #search-helper button:hover { border-color: #717171; } diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 75c7bd45a2949..d3ec047eec026 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -6,34 +6,35 @@ (function() { // This mapping table should match the discriminants of // `rustdoc::formats::item_type::ItemType` type in Rust. -const itemTypes = [ - "mod", - "externcrate", - "import", - "struct", - "enum", - "fn", - "type", - "static", - "trait", - "impl", - "tymethod", - "method", - "structfield", - "variant", - "macro", - "primitive", - "associatedtype", - "constant", - "associatedconstant", - "union", - "foreigntype", - "keyword", - "existential", - "attr", - "derive", - "traitalias", +const itemTypesMatching = [ + ["module", "mod"], + ["extern crate", "externcrate"], + ["import", "import"], + ["struct", "struct"], + ["enum", "enum"], + ["function", "fn"], + ["type", "type"], + ["static", "static"], + ["trait", "trait"], + ["impl", "impl"], + ["trait method", "tymethod"], + ["method", "method"], + ["struct field", "structfield"], + ["enum variant", "variant"], + ["macro", "macro"], + ["primitive", "primitive"], + ["associated type", "associatedtype"], + ["constant", "constant"], + ["associated constant", "associatedconstant"], + ["union", "union"], + ["foreign type", "foreigntype"], + ["keyword", "keyword"], + ["existential", "existential"], + ["attribute", "attr"], + ["derive", "derive"], + ["trait alias", "traitalias"], ]; +const itemTypes = itemTypesMatching.map(elem => elem[1]); // used for special search precedence const TY_PRIMITIVE = itemTypes.indexOf("primitive"); @@ -111,6 +112,157 @@ function levenshtein(s1, s2) { return s1_len + s2_len; } +function extractInformationFromElem(container, className) { + return Array.from(container.querySelectorAll(`.${className} .argument`)).map(elem => { + const text = elem.querySelector(".arg").value.trim(); + const generics = elem.querySelector(".generic").value.trim(); + + if (generics.length !== 0) { + return `${text}<${generics}>`; + } + return text; + }).filter(text => text.length !== 0); +} + +function generateSearch(literalSearch) { + const container = document.getElementById("search-helper"); + const filter = container.querySelector("select").value; + const args = extractInformationFromElem(container, "search-arguments"); + const returned = extractInformationFromElem(container, "search-returned"); + + let query = ""; + + if (args.length === 0 && returned.length === 0) { + return; + } + + if (filter.length !== 0) { + query += `${filter}:`; + } + + if (literalSearch) { + if (args.length !== 0) { + query += `"${args[0]}"`; + } else { + if (query.length !== 0) { + query += " "; + } + query += `-> "${returned[0]}"`; + } + } else { + query += args.join(", "); + if (returned.length !== 0) { + if (query.length !== 0) { + query += " "; + } + query += `-> ${returned.join(", ")}`; + } + } + document.querySelector(".search-container .search-input").value = query; + // Now collapsing the advanced search. + container.removeAttribute("open"); +} + +function addAdvancedSearchUI() { + const filters = itemTypesMatching.map(elem => `${elem[0]}`); + const detailsElem = document.createElement("details"); + let literalSearch = false; + + detailsElem.id = "search-helper"; + detailsElem.innerHTML = `\ +Advanced search helper + + + <no filter> + ${filters.join("")} + + + + Enable literal search + + + Arguments + + + + Generics for this argument + + + + + Add new argument + Remove last argument + + + + Returned types + + + + Generics for this returned type + + + + + Add new returned value + Remove last returned value + + + + Generate and run search + Clear search + +`; + + onEachLazy(detailsElem.querySelectorAll(".search-buttons .add"), elem => { + elem.addEventListener("click", () => { + const parent = elem.parentElement; + const container = parent.parentElement; + const newElem = container.querySelector(".argument").cloneNode(true); + onEachLazy(newElem.querySelectorAll("input"), elem => { + elem.value = ""; + }); + parent.insertAdjacentElement("beforebegin", newElem); + parent.querySelector(".remove").disabled = false; + }); + }); + onEachLazy(detailsElem.querySelectorAll(".search-buttons .remove"), elem => { + elem.addEventListener("click", () => { + const container = elem.parentElement.parentElement; + const elemToRemove = Array.from(container.querySelectorAll(".argument")).pop(); + container.removeChild(elemToRemove); + elem.disabled = container.querySelectorAll(".argument").length < 2; + }); + }); + detailsElem.querySelector(".clear-search").addEventListener("click", () => { + const elem = document.getElementById("search-helper"); + elem.parentNode.removeChild(elem); + addAdvancedSearchUI(); + }); + detailsElem.querySelector(".search-literal").addEventListener("click", () => { + const container = document.getElementById("search-helper"); + const elem = container.querySelector(".search-literal input"); + elem.checked = !elem.checked; + literalSearch = elem.checked; + const call = literalSearch ? addClass : removeClass; + onEachLazy(container.querySelectorAll(".argument:not(:first-of-type)"), elem => { + call(elem, "hidden"); + }); + }); + detailsElem.querySelector(".search-literal input").addEventListener("change", event => { + literalSearch = event.target.value; + }); + detailsElem.querySelector(".run-search").addEventListener("click", () => { + generateSearch(literalSearch); + }); + detailsElem.querySelector("form").addEventListener("submit", event => { + event.preventDefault(); + generateSearch(literalSearch); + }); + + document.querySelector(".search-form").parentElement.appendChild(detailsElem); +} + function initSearch(rawSearchIndex) { const MAX_LEV_DISTANCE = 3; const MAX_RESULTS = 200; @@ -2287,6 +2439,7 @@ if (typeof window !== "undefined") { if (window.searchIndex !== undefined) { initSearch(window.searchIndex); } + addAdvancedSearchUI(); } else { // Running in Node, not a browser. Run initSearch just to produce the // exports.