PageRenderTime 58ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/src/librustdoc/html/static/main.js

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