]> git.proxmox.com Git - rustc.git/blob - src/librustdoc/html/static/main.js
New upstream version 1.13.0+dfsg1
[rustc.git] / src / librustdoc / html / static / main.js
1 // Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 /*jslint browser: true, es5: true */
12 /*globals $: true, rootPath: true */
13
14 (function() {
15 "use strict";
16
17 // This mapping table should match the discriminants of
18 // `rustdoc::html::item_type::ItemType` type in Rust.
19 var itemTypes = ["mod",
20 "externcrate",
21 "import",
22 "struct",
23 "enum",
24 "fn",
25 "type",
26 "static",
27 "trait",
28 "impl",
29 "tymethod",
30 "method",
31 "structfield",
32 "variant",
33 "macro",
34 "primitive",
35 "associatedtype",
36 "constant",
37 "associatedconstant",
38 "union"];
39
40 // used for special search precedence
41 var TY_PRIMITIVE = itemTypes.indexOf("primitive");
42
43 $('.js-only').removeClass('js-only');
44
45 function getQueryStringParams() {
46 var params = {};
47 window.location.search.substring(1).split("&").
48 map(function(s) {
49 var pair = s.split("=");
50 params[decodeURIComponent(pair[0])] =
51 typeof pair[1] === "undefined" ?
52 null : decodeURIComponent(pair[1]);
53 });
54 return params;
55 }
56
57 function browserSupportsHistoryApi() {
58 return document.location.protocol != "file:" &&
59 window.history && typeof window.history.pushState === "function";
60 }
61
62 function highlightSourceLines(ev) {
63 var i, from, to, match = window.location.hash.match(/^#?(\d+)(?:-(\d+))?$/);
64 if (match) {
65 from = parseInt(match[1], 10);
66 to = Math.min(50000, parseInt(match[2] || match[1], 10));
67 from = Math.min(from, to);
68 if ($('#' + from).length === 0) {
69 return;
70 }
71 if (ev === null) { $('#' + from)[0].scrollIntoView(); };
72 $('.line-numbers span').removeClass('line-highlighted');
73 for (i = from; i <= to; ++i) {
74 $('#' + i).addClass('line-highlighted');
75 }
76 }
77 }
78 highlightSourceLines(null);
79 $(window).on('hashchange', highlightSourceLines);
80
81 // Gets the human-readable string for the virtual-key code of the
82 // given KeyboardEvent, ev.
83 //
84 // This function is meant as a polyfill for KeyboardEvent#key,
85 // since it is not supported in Trident. We also test for
86 // KeyboardEvent#keyCode because the handleShortcut handler is
87 // also registered for the keydown event, because Blink doesn't fire
88 // keypress on hitting the Escape key.
89 //
90 // So I guess you could say things are getting pretty interoperable.
91 function getVirtualKey(ev) {
92 if ("key" in ev && typeof ev.key != "undefined")
93 return ev.key;
94
95 var c = ev.charCode || ev.keyCode;
96 if (c == 27)
97 return "Escape";
98 return String.fromCharCode(c);
99 }
100
101 function handleShortcut(ev) {
102 if (document.activeElement.tagName == "INPUT")
103 return;
104
105 // Don't interfere with browser shortcuts
106 if (ev.ctrlKey || ev.altKey || ev.metaKey)
107 return;
108
109 switch (getVirtualKey(ev)) {
110 case "Escape":
111 if (!$("#help").hasClass("hidden")) {
112 ev.preventDefault();
113 $("#help").addClass("hidden");
114 $("body").removeClass("blur");
115 } else if (!$("#search").hasClass("hidden")) {
116 ev.preventDefault();
117 $("#search").addClass("hidden");
118 $("#main").removeClass("hidden");
119 }
120 break;
121
122 case "s":
123 case "S":
124 ev.preventDefault();
125 focusSearchBar();
126 break;
127
128 case "+":
129 ev.preventDefault();
130 toggleAllDocs();
131 break;
132
133 case "?":
134 if (ev.shiftKey && $("#help").hasClass("hidden")) {
135 ev.preventDefault();
136 $("#help").removeClass("hidden");
137 $("body").addClass("blur");
138 }
139 break;
140 }
141 }
142
143 $(document).on("keypress", handleShortcut);
144 $(document).on("keydown", handleShortcut);
145 $(document).on("click", function(ev) {
146 if (!$(ev.target).closest("#help > div").length) {
147 $("#help").addClass("hidden");
148 $("body").removeClass("blur");
149 }
150 });
151
152 $('.version-selector').on('change', function() {
153 var i, match,
154 url = document.location.href,
155 stripped = '',
156 len = rootPath.match(/\.\.\//g).length + 1;
157
158 for (i = 0; i < len; ++i) {
159 match = url.match(/\/[^\/]*$/);
160 if (i < len - 1) {
161 stripped = match[0] + stripped;
162 }
163 url = url.substring(0, url.length - match[0].length);
164 }
165
166 url += '/' + $('.version-selector').val() + stripped;
167
168 document.location.href = url;
169 });
170
171 /**
172 * A function to compute the Levenshtein distance between two strings
173 * Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported
174 * Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode
175 * This code is an unmodified version of the code written by Marco de Wit
176 * and was found at http://stackoverflow.com/a/18514751/745719
177 */
178 var levenshtein = (function() {
179 var row2 = [];
180 return function(s1, s2) {
181 if (s1 === s2) {
182 return 0;
183 }
184 var s1_len = s1.length, s2_len = s2.length;
185 if (s1_len && s2_len) {
186 var i1 = 0, i2 = 0, a, b, c, c2, row = row2;
187 while (i1 < s1_len) {
188 row[i1] = ++i1;
189 }
190 while (i2 < s2_len) {
191 c2 = s2.charCodeAt(i2);
192 a = i2;
193 ++i2;
194 b = i2;
195 for (i1 = 0; i1 < s1_len; ++i1) {
196 c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
197 a = row[i1];
198 b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
199 row[i1] = b;
200 }
201 }
202 return b;
203 }
204 return s1_len + s2_len;
205 };
206 })();
207
208 function initSearch(rawSearchIndex) {
209 var currentResults, index, searchIndex;
210 var MAX_LEV_DISTANCE = 3;
211 var params = getQueryStringParams();
212
213 // Populate search bar with query string search term when provided,
214 // but only if the input bar is empty. This avoid the obnoxious issue
215 // where you start trying to do a search, and the index loads, and
216 // suddenly your search is gone!
217 if ($(".search-input")[0].value === "") {
218 $(".search-input")[0].value = params.search || '';
219 }
220
221 /**
222 * Executes the query and builds an index of results
223 * @param {[Object]} query [The user query]
224 * @param {[type]} max [The maximum results returned]
225 * @param {[type]} searchWords [The list of search words to query
226 * against]
227 * @return {[type]} [A search index of results]
228 */
229 function execQuery(query, max, searchWords) {
230 var valLower = query.query.toLowerCase(),
231 val = valLower,
232 typeFilter = itemTypeFromName(query.type),
233 results = [],
234 split = valLower.split("::");
235
236 // remove empty keywords
237 for (var j = 0; j < split.length; ++j) {
238 split[j].toLowerCase();
239 if (split[j] === "") {
240 split.splice(j, 1);
241 }
242 }
243
244 function typePassesFilter(filter, type) {
245 // No filter
246 if (filter < 0) return true;
247
248 // Exact match
249 if (filter === type) return true;
250
251 // Match related items
252 var name = itemTypes[type];
253 switch (itemTypes[filter]) {
254 case "constant":
255 return (name == "associatedconstant");
256 case "fn":
257 return (name == "method" || name == "tymethod");
258 case "type":
259 return (name == "primitive");
260 }
261
262 // No match
263 return false;
264 }
265
266 // quoted values mean literal search
267 var nSearchWords = searchWords.length;
268 if ((val.charAt(0) === "\"" || val.charAt(0) === "'") &&
269 val.charAt(val.length - 1) === val.charAt(0))
270 {
271 val = val.substr(1, val.length - 2);
272 for (var i = 0; i < nSearchWords; ++i) {
273 if (searchWords[i] === val) {
274 // filter type: ... queries
275 if (typePassesFilter(typeFilter, searchIndex[i].ty)) {
276 results.push({id: i, index: -1});
277 }
278 }
279 if (results.length === max) {
280 break;
281 }
282 }
283 // searching by type
284 } else if (val.search("->") > -1) {
285 var trimmer = function (s) { return s.trim(); };
286 var parts = val.split("->").map(trimmer);
287 var input = parts[0];
288 // sort inputs so that order does not matter
289 var inputs = input.split(",").map(trimmer).sort().toString();
290 var output = parts[1];
291
292 for (var i = 0; i < nSearchWords; ++i) {
293 var type = searchIndex[i].type;
294 if (!type) {
295 continue;
296 }
297
298 // sort index inputs so that order does not matter
299 var typeInputs = type.inputs.map(function (input) {
300 return input.name;
301 }).sort();
302
303 // allow searching for void (no output) functions as well
304 var typeOutput = type.output ? type.output.name : "";
305 if ((inputs === "*" || inputs === typeInputs.toString()) &&
306 (output === "*" || output == typeOutput)) {
307 results.push({id: i, index: -1, dontValidate: true});
308 }
309 }
310 } else {
311 // gather matching search results up to a certain maximum
312 val = val.replace(/\_/g, "");
313 for (var i = 0; i < split.length; ++i) {
314 for (var j = 0; j < nSearchWords; ++j) {
315 var lev_distance;
316 if (searchWords[j].indexOf(split[i]) > -1 ||
317 searchWords[j].indexOf(val) > -1 ||
318 searchWords[j].replace(/_/g, "").indexOf(val) > -1)
319 {
320 // filter type: ... queries
321 if (typePassesFilter(typeFilter, searchIndex[j].ty)) {
322 results.push({
323 id: j,
324 index: searchWords[j].replace(/_/g, "").indexOf(val),
325 lev: 0,
326 });
327 }
328 } else if (
329 (lev_distance = levenshtein(searchWords[j], val)) <=
330 MAX_LEV_DISTANCE) {
331 if (typePassesFilter(typeFilter, searchIndex[j].ty)) {
332 results.push({
333 id: j,
334 index: 0,
335 // we want lev results to go lower than others
336 lev: lev_distance,
337 });
338 }
339 }
340 if (results.length === max) {
341 break;
342 }
343 }
344 }
345 }
346
347 var nresults = results.length;
348 for (var i = 0; i < nresults; ++i) {
349 results[i].word = searchWords[results[i].id];
350 results[i].item = searchIndex[results[i].id] || {};
351 }
352 // if there are no results then return to default and fail
353 if (results.length === 0) {
354 return [];
355 }
356
357 results.sort(function sortResults(aaa, bbb) {
358 var a, b;
359
360 // Sort by non levenshtein results and then levenshtein results by the distance
361 // (less changes required to match means higher rankings)
362 a = (aaa.lev);
363 b = (bbb.lev);
364 if (a !== b) { return a - b; }
365
366 // sort by crate (non-current crate goes later)
367 a = (aaa.item.crate !== window.currentCrate);
368 b = (bbb.item.crate !== window.currentCrate);
369 if (a !== b) { return a - b; }
370
371 // sort by exact match (mismatch goes later)
372 a = (aaa.word !== valLower);
373 b = (bbb.word !== valLower);
374 if (a !== b) { return a - b; }
375
376 // sort by item name length (longer goes later)
377 a = aaa.word.length;
378 b = bbb.word.length;
379 if (a !== b) { return a - b; }
380
381 // sort by item name (lexicographically larger goes later)
382 a = aaa.word;
383 b = bbb.word;
384 if (a !== b) { return (a > b ? +1 : -1); }
385
386 // sort by index of keyword in item name (no literal occurrence goes later)
387 a = (aaa.index < 0);
388 b = (bbb.index < 0);
389 if (a !== b) { return a - b; }
390 // (later literal occurrence, if any, goes later)
391 a = aaa.index;
392 b = bbb.index;
393 if (a !== b) { return a - b; }
394
395 // special precedence for primitive pages
396 if ((aaa.item.ty === TY_PRIMITIVE) && (bbb.item.ty !== TY_PRIMITIVE)) {
397 return -1;
398 }
399 if ((bbb.item.ty === TY_PRIMITIVE) && (aaa.item.ty !== TY_PRIMITIVE)) {
400 return 1;
401 }
402
403 // sort by description (no description goes later)
404 a = (aaa.item.desc === '');
405 b = (bbb.item.desc === '');
406 if (a !== b) { return a - b; }
407
408 // sort by type (later occurrence in `itemTypes` goes later)
409 a = aaa.item.ty;
410 b = bbb.item.ty;
411 if (a !== b) { return a - b; }
412
413 // sort by path (lexicographically larger goes later)
414 a = aaa.item.path;
415 b = bbb.item.path;
416 if (a !== b) { return (a > b ? +1 : -1); }
417
418 // que sera, sera
419 return 0;
420 });
421
422 // remove duplicates, according to the data provided
423 for (var i = results.length - 1; i > 0; i -= 1) {
424 if (results[i].word === results[i - 1].word &&
425 results[i].item.ty === results[i - 1].item.ty &&
426 results[i].item.path === results[i - 1].item.path &&
427 (results[i].item.parent || {}).name === (results[i - 1].item.parent || {}).name)
428 {
429 results[i].id = -1;
430 }
431 }
432 for (var i = 0; i < results.length; ++i) {
433 var result = results[i],
434 name = result.item.name.toLowerCase(),
435 path = result.item.path.toLowerCase(),
436 parent = result.item.parent;
437
438 // this validation does not make sense when searching by types
439 if (result.dontValidate) {
440 continue;
441 }
442
443 var valid = validateResult(name, path, split, parent);
444 if (!valid) {
445 result.id = -1;
446 }
447 }
448 return results;
449 }
450
451 /**
452 * Validate performs the following boolean logic. For example:
453 * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
454 * exists in (name || path || parent) OR => ("file" && "open") exists in
455 * (name || path )
456 *
457 * This could be written functionally, but I wanted to minimise
458 * functions on stack.
459 *
460 * @param {[string]} name [The name of the result]
461 * @param {[string]} path [The path of the result]
462 * @param {[string]} keys [The keys to be used (["file", "open"])]
463 * @param {[object]} parent [The parent of the result]
464 * @return {[boolean]} [Whether the result is valid or not]
465 */
466 function validateResult(name, path, keys, parent) {
467 for (var i = 0; i < keys.length; ++i) {
468 // each check is for validation so we negate the conditions and invalidate
469 if (!(
470 // check for an exact name match
471 name.toLowerCase().indexOf(keys[i]) > -1 ||
472 // then an exact path match
473 path.toLowerCase().indexOf(keys[i]) > -1 ||
474 // next if there is a parent, check for exact parent match
475 (parent !== undefined &&
476 parent.name.toLowerCase().indexOf(keys[i]) > -1) ||
477 // lastly check to see if the name was a levenshtein match
478 levenshtein(name.toLowerCase(), keys[i]) <=
479 MAX_LEV_DISTANCE)) {
480 return false;
481 }
482 }
483 return true;
484 }
485
486 function getQuery() {
487 var matches, type, query, raw = $('.search-input').val();
488 query = raw;
489
490 matches = query.match(/^(fn|mod|struct|enum|trait|type|const|macro)\s*:\s*/i);
491 if (matches) {
492 type = matches[1].replace(/^const$/, 'constant');
493 query = query.substring(matches[0].length);
494 }
495
496 return {
497 raw: raw,
498 query: query,
499 type: type,
500 id: query + type
501 };
502 }
503
504 function initSearchNav() {
505 var hoverTimeout, $results = $('.search-results .result');
506
507 $results.on('click', function() {
508 var dst = $(this).find('a')[0];
509 if (window.location.pathname === dst.pathname) {
510 $('#search').addClass('hidden');
511 $('#main').removeClass('hidden');
512 document.location.href = dst.href;
513 }
514 }).on('mouseover', function() {
515 var $el = $(this);
516 clearTimeout(hoverTimeout);
517 hoverTimeout = setTimeout(function() {
518 $results.removeClass('highlighted');
519 $el.addClass('highlighted');
520 }, 20);
521 });
522
523 $(document).off('keydown.searchnav');
524 $(document).on('keydown.searchnav', function(e) {
525 var $active = $results.filter('.highlighted');
526
527 if (e.which === 38) { // up
528 if (!$active.length || !$active.prev()) {
529 return;
530 }
531
532 $active.prev().addClass('highlighted');
533 $active.removeClass('highlighted');
534 } else if (e.which === 40) { // down
535 if (!$active.length) {
536 $results.first().addClass('highlighted');
537 } else if ($active.next().length) {
538 $active.next().addClass('highlighted');
539 $active.removeClass('highlighted');
540 }
541 } else if (e.which === 13) { // return
542 if ($active.length) {
543 document.location.href = $active.find('a').prop('href');
544 }
545 } else {
546 $active.removeClass('highlighted');
547 }
548 });
549 }
550
551 function escape(content) {
552 return $('<h1/>').text(content).html();
553 }
554
555 function showResults(results) {
556 var output, shown, query = getQuery();
557
558 currentResults = query.id;
559 output = '<h1>Results for ' + escape(query.query) +
560 (query.type ? ' (type: ' + escape(query.type) + ')' : '') + '</h1>';
561 output += '<table class="search-results">';
562
563 if (results.length > 0) {
564 shown = [];
565
566 results.forEach(function(item) {
567 var name, type, href, displayPath;
568
569 if (shown.indexOf(item) !== -1) {
570 return;
571 }
572
573 shown.push(item);
574 name = item.name;
575 type = itemTypes[item.ty];
576
577 if (type === 'mod') {
578 displayPath = item.path + '::';
579 href = rootPath + item.path.replace(/::/g, '/') + '/' +
580 name + '/index.html';
581 } else if (type === "primitive") {
582 displayPath = "";
583 href = rootPath + item.path.replace(/::/g, '/') +
584 '/' + type + '.' + name + '.html';
585 } else if (type === "externcrate") {
586 displayPath = "";
587 href = rootPath + name + '/index.html';
588 } else if (item.parent !== undefined) {
589 var myparent = item.parent;
590 var anchor = '#' + type + '.' + name;
591 var parentType = itemTypes[myparent.ty];
592 if (parentType === "primitive") {
593 displayPath = myparent.name + '::';
594 } else {
595 displayPath = item.path + '::' + myparent.name + '::';
596 }
597 href = rootPath + item.path.replace(/::/g, '/') +
598 '/' + parentType +
599 '.' + myparent.name +
600 '.html' + anchor;
601 } else {
602 displayPath = item.path + '::';
603 href = rootPath + item.path.replace(/::/g, '/') +
604 '/' + type + '.' + name + '.html';
605 }
606
607 output += '<tr class="' + type + ' result"><td>' +
608 '<a href="' + href + '">' +
609 displayPath + '<span class="' + type + '">' +
610 name + '</span></a></td><td>' +
611 '<a href="' + href + '">' +
612 '<span class="desc">' + item.desc +
613 '&nbsp;</span></a></td></tr>';
614 });
615 } else {
616 output += 'No results :( <a href="https://duckduckgo.com/?q=' +
617 encodeURIComponent('rust ' + query.query) +
618 '">Try on DuckDuckGo?</a>';
619 }
620
621 output += "</p>";
622 $('#main.content').addClass('hidden');
623 $('#search.content').removeClass('hidden').html(output);
624 $('#search .desc').width($('#search').width() - 40 -
625 $('#search td:first-child').first().width());
626 initSearchNav();
627 }
628
629 function search(e) {
630 var query,
631 filterdata = [],
632 obj, i, len,
633 results = [],
634 maxResults = 200,
635 resultIndex;
636 var params = getQueryStringParams();
637
638 query = getQuery();
639 if (e) {
640 e.preventDefault();
641 }
642
643 if (!query.query || query.id === currentResults) {
644 return;
645 }
646
647 // Update document title to maintain a meaningful browser history
648 $(document).prop("title", "Results for " + query.query + " - Rust");
649
650 // Because searching is incremental by character, only the most
651 // recent search query is added to the browser history.
652 if (browserSupportsHistoryApi()) {
653 if (!history.state && !params.search) {
654 history.pushState(query, "", "?search=" +
655 encodeURIComponent(query.raw));
656 } else {
657 history.replaceState(query, "", "?search=" +
658 encodeURIComponent(query.raw));
659 }
660 }
661
662 resultIndex = execQuery(query, 20000, index);
663 len = resultIndex.length;
664 for (i = 0; i < len; ++i) {
665 if (resultIndex[i].id > -1) {
666 obj = searchIndex[resultIndex[i].id];
667 filterdata.push([obj.name, obj.ty, obj.path, obj.desc]);
668 results.push(obj);
669 }
670 if (results.length >= maxResults) {
671 break;
672 }
673 }
674
675 showResults(results);
676 }
677
678 function itemTypeFromName(typename) {
679 for (var i = 0; i < itemTypes.length; ++i) {
680 if (itemTypes[i] === typename) { return i; }
681 }
682 return -1;
683 }
684
685 function buildIndex(rawSearchIndex) {
686 searchIndex = [];
687 var searchWords = [];
688 for (var crate in rawSearchIndex) {
689 if (!rawSearchIndex.hasOwnProperty(crate)) { continue; }
690
691 searchWords.push(crate);
692 searchIndex.push({
693 crate: crate,
694 ty: 1, // == ExternCrate
695 name: crate,
696 path: "",
697 desc: rawSearchIndex[crate].doc,
698 type: null,
699 });
700
701 // an array of [(Number) item type,
702 // (String) name,
703 // (String) full path or empty string for previous path,
704 // (String) description,
705 // (Number | null) the parent path index to `paths`]
706 // (Object | null) the type of the function (if any)
707 var items = rawSearchIndex[crate].items;
708 // an array of [(Number) item type,
709 // (String) name]
710 var paths = rawSearchIndex[crate].paths;
711
712 // convert `paths` into an object form
713 var len = paths.length;
714 for (var i = 0; i < len; ++i) {
715 paths[i] = {ty: paths[i][0], name: paths[i][1]};
716 }
717
718 // convert `items` into an object form, and construct word indices.
719 //
720 // before any analysis is performed lets gather the search terms to
721 // search against apart from the rest of the data. This is a quick
722 // operation that is cached for the life of the page state so that
723 // all other search operations have access to this cached data for
724 // faster analysis operations
725 var len = items.length;
726 var lastPath = "";
727 for (var i = 0; i < len; ++i) {
728 var rawRow = items[i];
729 var row = {crate: crate, ty: rawRow[0], name: rawRow[1],
730 path: rawRow[2] || lastPath, desc: rawRow[3],
731 parent: paths[rawRow[4]], type: rawRow[5]};
732 searchIndex.push(row);
733 if (typeof row.name === "string") {
734 var word = row.name.toLowerCase();
735 searchWords.push(word);
736 } else {
737 searchWords.push("");
738 }
739 lastPath = row.path;
740 }
741 }
742 return searchWords;
743 }
744
745 function startSearch() {
746 var searchTimeout;
747 $(".search-input").on("keyup input",function() {
748 clearTimeout(searchTimeout);
749 if ($(this).val().length === 0) {
750 if (browserSupportsHistoryApi()) {
751 history.replaceState("", "std - Rust", "?search=");
752 }
753 $('#main.content').removeClass('hidden');
754 $('#search.content').addClass('hidden');
755 } else {
756 searchTimeout = setTimeout(search, 500);
757 }
758 });
759 $('.search-form').on('submit', function(e){
760 e.preventDefault();
761 clearTimeout(searchTimeout);
762 search();
763 });
764 $('.search-input').on('change paste', function(e) {
765 // Do NOT e.preventDefault() here. It will prevent pasting.
766 clearTimeout(searchTimeout);
767 // zero-timeout necessary here because at the time of event handler execution the
768 // pasted content is not in the input field yet. Shouldn’t make any difference for
769 // change, though.
770 setTimeout(search, 0);
771 });
772
773 // Push and pop states are used to add search results to the browser
774 // history.
775 if (browserSupportsHistoryApi()) {
776 // Store the previous <title> so we can revert back to it later.
777 var previousTitle = $(document).prop("title");
778
779 $(window).on('popstate', function(e) {
780 var params = getQueryStringParams();
781 // When browsing back from search results the main page
782 // visibility must be reset.
783 if (!params.search) {
784 $('#main.content').removeClass('hidden');
785 $('#search.content').addClass('hidden');
786 }
787 // Revert to the previous title manually since the History
788 // API ignores the title parameter.
789 $(document).prop("title", previousTitle);
790 // When browsing forward to search results the previous
791 // search will be repeated, so the currentResults are
792 // cleared to ensure the search is successful.
793 currentResults = null;
794 // Synchronize search bar with query string state and
795 // perform the search. This will empty the bar if there's
796 // nothing there, which lets you really go back to a
797 // previous state with nothing in the bar.
798 $('.search-input').val(params.search);
799 // Some browsers fire 'onpopstate' for every page load
800 // (Chrome), while others fire the event only when actually
801 // popping a state (Firefox), which is why search() is
802 // called both here and at the end of the startSearch()
803 // function.
804 search();
805 });
806 }
807 search();
808 }
809
810 function plainSummaryLine(markdown) {
811 markdown.replace(/\n/g, ' ')
812 .replace(/'/g, "\'")
813 .replace(/^#+? (.+?)/, "$1")
814 .replace(/\[(.*?)\]\(.*?\)/g, "$1")
815 .replace(/\[(.*?)\]\[.*?\]/g, "$1");
816 }
817
818 index = buildIndex(rawSearchIndex);
819 startSearch();
820
821 // Draw a convenient sidebar of known crates if we have a listing
822 if (rootPath === '../') {
823 var sidebar = $('.sidebar');
824 var div = $('<div>').attr('class', 'block crate');
825 div.append($('<h3>').text('Crates'));
826 var ul = $('<ul>').appendTo(div);
827
828 var crates = [];
829 for (var crate in rawSearchIndex) {
830 if (!rawSearchIndex.hasOwnProperty(crate)) { continue; }
831 crates.push(crate);
832 }
833 crates.sort();
834 for (var i = 0; i < crates.length; ++i) {
835 var klass = 'crate';
836 if (crates[i] === window.currentCrate) {
837 klass += ' current';
838 }
839 if (rawSearchIndex[crates[i]].items[0]) {
840 var desc = rawSearchIndex[crates[i]].items[0][3];
841 var link = $('<a>', {'href': '../' + crates[i] + '/index.html',
842 'title': plainSummaryLine(desc),
843 'class': klass}).text(crates[i]);
844 ul.append($('<li>').append(link));
845 }
846 }
847 sidebar.append(div);
848 }
849 }
850
851 window.initSearch = initSearch;
852
853 // delayed sidebar rendering.
854 function initSidebarItems(items) {
855 var sidebar = $('.sidebar');
856 var current = window.sidebarCurrent;
857
858 function block(shortty, longty) {
859 var filtered = items[shortty];
860 if (!filtered) { return; }
861
862 var div = $('<div>').attr('class', 'block ' + shortty);
863 div.append($('<h3>').text(longty));
864 var ul = $('<ul>').appendTo(div);
865
866 for (var i = 0; i < filtered.length; ++i) {
867 var item = filtered[i];
868 var name = item[0];
869 var desc = item[1]; // can be null
870
871 var klass = shortty;
872 if (name === current.name && shortty === current.ty) {
873 klass += ' current';
874 }
875 var path;
876 if (shortty === 'mod') {
877 path = name + '/index.html';
878 } else {
879 path = shortty + '.' + name + '.html';
880 }
881 var link = $('<a>', {'href': current.relpath + path,
882 'title': desc,
883 'class': klass}).text(name);
884 ul.append($('<li>').append(link));
885 }
886 sidebar.append(div);
887 }
888
889 block("primitive", "Primitive Types");
890 block("mod", "Modules");
891 block("macro", "Macros");
892 block("struct", "Structs");
893 block("enum", "Enums");
894 block("constant", "Constants");
895 block("static", "Statics");
896 block("trait", "Traits");
897 block("fn", "Functions");
898 block("type", "Type Definitions");
899 }
900
901 window.initSidebarItems = initSidebarItems;
902
903 window.register_implementors = function(imp) {
904 var list = $('#implementors-list');
905 var libs = Object.getOwnPropertyNames(imp);
906 for (var i = 0; i < libs.length; ++i) {
907 if (libs[i] === currentCrate) { continue; }
908 var structs = imp[libs[i]];
909 for (var j = 0; j < structs.length; ++j) {
910 var code = $('<code>').append(structs[j]);
911 $.each(code.find('a'), function(idx, a) {
912 var href = $(a).attr('href');
913 if (href && href.indexOf('http') !== 0) {
914 $(a).attr('href', rootPath + href);
915 }
916 });
917 var li = $('<li>').append(code);
918 list.append(li);
919 }
920 }
921 };
922 if (window.pending_implementors) {
923 window.register_implementors(window.pending_implementors);
924 }
925
926 // See documentation in html/render.rs for what this is doing.
927 var query = getQueryStringParams();
928 if (query['gotosrc']) {
929 window.location = $('#src-' + query['gotosrc']).attr('href');
930 }
931 if (query['gotomacrosrc']) {
932 window.location = $('.srclink').attr('href');
933 }
934
935 function labelForToggleButton(sectionIsCollapsed) {
936 if (sectionIsCollapsed) {
937 // button will expand the section
938 return "+";
939 }
940 // button will collapse the section
941 // note that this text is also set in the HTML template in render.rs
942 return "\u2212"; // "\u2212" is '' minus sign
943 }
944
945 function toggleAllDocs() {
946 var toggle = $("#toggle-all-docs");
947 if (toggle.hasClass("will-expand")) {
948 toggle.removeClass("will-expand");
949 toggle.children(".inner").text(labelForToggleButton(false));
950 toggle.attr("title", "collapse all docs");
951 $(".docblock").show();
952 $(".toggle-label").hide();
953 $(".toggle-wrapper").removeClass("collapsed");
954 $(".collapse-toggle").children(".inner").text(labelForToggleButton(false));
955 } else {
956 toggle.addClass("will-expand");
957 toggle.children(".inner").text(labelForToggleButton(true));
958 toggle.attr("title", "expand all docs");
959 $(".docblock").hide();
960 $(".toggle-label").show();
961 $(".toggle-wrapper").addClass("collapsed");
962 $(".collapse-toggle").children(".inner").text(labelForToggleButton(true));
963 }
964 }
965
966 $("#toggle-all-docs").on("click", toggleAllDocs);
967
968 $(document).on("click", ".collapse-toggle", function() {
969 var toggle = $(this);
970 var relatedDoc = toggle.parent().next();
971 if (relatedDoc.is(".stability")) {
972 relatedDoc = relatedDoc.next();
973 }
974 if (relatedDoc.is(".docblock")) {
975 if (relatedDoc.is(":visible")) {
976 relatedDoc.slideUp({duration: 'fast', easing: 'linear'});
977 toggle.parent(".toggle-wrapper").addClass("collapsed");
978 toggle.children(".inner").text(labelForToggleButton(true));
979 toggle.children(".toggle-label").fadeIn();
980 } else {
981 relatedDoc.slideDown({duration: 'fast', easing: 'linear'});
982 toggle.parent(".toggle-wrapper").removeClass("collapsed");
983 toggle.children(".inner").text(labelForToggleButton(false));
984 toggle.children(".toggle-label").hide();
985 }
986 }
987 });
988
989 $(function() {
990 var toggle = $("<a/>", {'href': 'javascript:void(0)', 'class': 'collapse-toggle'})
991 .html("[<span class='inner'></span>]");
992 toggle.children(".inner").text(labelForToggleButton(false));
993
994 $(".method").each(function() {
995 if ($(this).next().is(".docblock") ||
996 ($(this).next().is(".stability") && $(this).next().next().is(".docblock"))) {
997 $(this).children().last().after(toggle.clone());
998 }
999 });
1000
1001 var mainToggle =
1002 $(toggle).append(
1003 $('<span/>', {'class': 'toggle-label'})
1004 .css('display', 'none')
1005 .html('&nbsp;Expand&nbsp;description'));
1006 var wrapper = $("<div class='toggle-wrapper'>").append(mainToggle);
1007 $("#main > .docblock").before(wrapper);
1008 });
1009
1010 $('pre.line-numbers').on('click', 'span', function() {
1011 var prev_id = 0;
1012
1013 function set_fragment(name) {
1014 if (browserSupportsHistoryApi()) {
1015 history.replaceState(null, null, '#' + name);
1016 $(window).trigger('hashchange');
1017 } else {
1018 location.replace('#' + name);
1019 }
1020 }
1021
1022 return function(ev) {
1023 var cur_id = parseInt(ev.target.id, 10);
1024
1025 if (ev.shiftKey && prev_id) {
1026 if (prev_id > cur_id) {
1027 var tmp = prev_id;
1028 prev_id = cur_id;
1029 cur_id = tmp;
1030 }
1031
1032 set_fragment(prev_id + '-' + cur_id);
1033 } else {
1034 prev_id = cur_id;
1035
1036 set_fragment(cur_id);
1037 }
1038 };
1039 }());
1040
1041 }());
1042
1043 // Sets the focus on the search bar at the top of the page
1044 function focusSearchBar() {
1045 $('.search-input').focus();
1046 }