• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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