1// From rust: 2/* global sourcesIndex */ 3 4// Local js definitions: 5/* global addClass, getCurrentValue, onEachLazy, removeClass, browserSupportsHistoryApi */ 6/* global updateLocalStorage, getVar */ 7 8"use strict"; 9 10(function() { 11 12const rootPath = getVar("root-path"); 13 14const NAME_OFFSET = 0; 15const DIRS_OFFSET = 1; 16const FILES_OFFSET = 2; 17 18// WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY 19// If you update this line, then you also need to update the media query with the same 20// warning in rustdoc.css 21const RUSTDOC_MOBILE_BREAKPOINT = 700; 22 23function closeSidebarIfMobile() { 24 if (window.innerWidth < RUSTDOC_MOBILE_BREAKPOINT) { 25 updateLocalStorage("source-sidebar-show", "false"); 26 } 27} 28 29function createDirEntry(elem, parent, fullPath, hasFoundFile) { 30 const dirEntry = document.createElement("details"); 31 const summary = document.createElement("summary"); 32 33 dirEntry.className = "dir-entry"; 34 35 fullPath += elem[NAME_OFFSET] + "/"; 36 37 summary.innerText = elem[NAME_OFFSET]; 38 dirEntry.appendChild(summary); 39 40 const folders = document.createElement("div"); 41 folders.className = "folders"; 42 if (elem[DIRS_OFFSET]) { 43 for (const dir of elem[DIRS_OFFSET]) { 44 if (createDirEntry(dir, folders, fullPath, false)) { 45 dirEntry.open = true; 46 hasFoundFile = true; 47 } 48 } 49 } 50 dirEntry.appendChild(folders); 51 52 const files = document.createElement("div"); 53 files.className = "files"; 54 if (elem[FILES_OFFSET]) { 55 const w = window.location.href.split("#")[0]; 56 for (const file_text of elem[FILES_OFFSET]) { 57 const file = document.createElement("a"); 58 file.innerText = file_text; 59 file.href = rootPath + "src/" + fullPath + file_text + ".html"; 60 file.addEventListener("click", closeSidebarIfMobile); 61 if (!hasFoundFile && w === file.href) { 62 file.className = "selected"; 63 dirEntry.open = true; 64 hasFoundFile = true; 65 } 66 files.appendChild(file); 67 } 68 } 69 dirEntry.appendChild(files); 70 parent.appendChild(dirEntry); 71 return hasFoundFile; 72} 73 74function toggleSidebar() { 75 const child = this.parentNode.children[0]; 76 if (child.innerText === ">") { 77 addClass(document.documentElement, "source-sidebar-expanded"); 78 child.innerText = "<"; 79 updateLocalStorage("source-sidebar-show", "true"); 80 } else { 81 removeClass(document.documentElement, "source-sidebar-expanded"); 82 child.innerText = ">"; 83 updateLocalStorage("source-sidebar-show", "false"); 84 } 85} 86 87function createSidebarToggle() { 88 const sidebarToggle = document.createElement("div"); 89 sidebarToggle.id = "src-sidebar-toggle"; 90 91 const inner = document.createElement("button"); 92 93 if (getCurrentValue("source-sidebar-show") === "true") { 94 inner.innerText = "<"; 95 } else { 96 inner.innerText = ">"; 97 } 98 inner.onclick = toggleSidebar; 99 100 sidebarToggle.appendChild(inner); 101 return sidebarToggle; 102} 103 104// This function is called from "source-files.js", generated in `html/render/write_shared.rs`. 105// eslint-disable-next-line no-unused-vars 106function createSourceSidebar() { 107 const container = document.querySelector("nav.sidebar"); 108 109 const sidebarToggle = createSidebarToggle(); 110 container.insertBefore(sidebarToggle, container.firstChild); 111 112 const sidebar = document.createElement("div"); 113 sidebar.id = "source-sidebar"; 114 115 let hasFoundFile = false; 116 117 const title = document.createElement("div"); 118 title.className = "title"; 119 title.innerText = "Files"; 120 sidebar.appendChild(title); 121 Object.keys(sourcesIndex).forEach(key => { 122 sourcesIndex[key][NAME_OFFSET] = key; 123 hasFoundFile = createDirEntry(sourcesIndex[key], sidebar, "", hasFoundFile); 124 }); 125 126 container.appendChild(sidebar); 127 // Focus on the current file in the source files sidebar. 128 const selected_elem = sidebar.getElementsByClassName("selected")[0]; 129 if (typeof selected_elem !== "undefined") { 130 selected_elem.focus(); 131 } 132} 133 134const lineNumbersRegex = /^#?(\d+)(?:-(\d+))?$/; 135 136function highlightSourceLines(match) { 137 if (typeof match === "undefined") { 138 match = window.location.hash.match(lineNumbersRegex); 139 } 140 if (!match) { 141 return; 142 } 143 let from = parseInt(match[1], 10); 144 let to = from; 145 if (typeof match[2] !== "undefined") { 146 to = parseInt(match[2], 10); 147 } 148 if (to < from) { 149 const tmp = to; 150 to = from; 151 from = tmp; 152 } 153 let elem = document.getElementById(from); 154 if (!elem) { 155 return; 156 } 157 const x = document.getElementById(from); 158 if (x) { 159 x.scrollIntoView(); 160 } 161 onEachLazy(document.getElementsByClassName("src-line-numbers"), e => { 162 onEachLazy(e.getElementsByTagName("a"), i_e => { 163 removeClass(i_e, "line-highlighted"); 164 }); 165 }); 166 for (let i = from; i <= to; ++i) { 167 elem = document.getElementById(i); 168 if (!elem) { 169 break; 170 } 171 addClass(elem, "line-highlighted"); 172 } 173} 174 175const handleSourceHighlight = (function() { 176 let prev_line_id = 0; 177 178 const set_fragment = name => { 179 const x = window.scrollX, 180 y = window.scrollY; 181 if (browserSupportsHistoryApi()) { 182 history.replaceState(null, null, "#" + name); 183 highlightSourceLines(); 184 } else { 185 location.replace("#" + name); 186 } 187 // Prevent jumps when selecting one or many lines 188 window.scrollTo(x, y); 189 }; 190 191 return ev => { 192 let cur_line_id = parseInt(ev.target.id, 10); 193 // This event handler is attached to the entire line number column, but it should only 194 // be run if one of the anchors is clicked. It also shouldn't do anything if the anchor 195 // is clicked with a modifier key (to open a new browser tab). 196 if (isNaN(cur_line_id) || 197 ev.ctrlKey || 198 ev.altKey || 199 ev.metaKey) { 200 return; 201 } 202 ev.preventDefault(); 203 204 if (ev.shiftKey && prev_line_id) { 205 // Swap selection if needed 206 if (prev_line_id > cur_line_id) { 207 const tmp = prev_line_id; 208 prev_line_id = cur_line_id; 209 cur_line_id = tmp; 210 } 211 212 set_fragment(prev_line_id + "-" + cur_line_id); 213 } else { 214 prev_line_id = cur_line_id; 215 216 set_fragment(cur_line_id); 217 } 218 }; 219}()); 220 221window.addEventListener("hashchange", () => { 222 const match = window.location.hash.match(lineNumbersRegex); 223 if (match) { 224 return highlightSourceLines(match); 225 } 226}); 227 228onEachLazy(document.getElementsByClassName("src-line-numbers"), el => { 229 el.addEventListener("click", handleSourceHighlight); 230}); 231 232highlightSourceLines(); 233 234window.createSourceSidebar = createSourceSidebar; 235})(); 236