PageRenderTime 58ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

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

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