]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/main.js
auto merge of #14874 : pcwalton/rust/enum-to-float-casts-part-deux, r=alexcrichton
[rust.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     var resizeTimeout, interval;
17
18     $('.js-only').removeClass('js-only');
19
20     function getQueryStringParams() {
21         var params = {};
22         window.location.search.substring(1).split("&").
23             map(function(s) {
24                 var pair = s.split("=");
25                 params[decodeURIComponent(pair[0])] =
26                     typeof pair[1] === "undefined" ?
27                             null : decodeURIComponent(pair[1]);
28             });
29         return params;
30     }
31
32     function browserSupportsHistoryApi() {
33         return window.history && typeof window.history.pushState === "function";
34     }
35
36     function resizeShortBlocks() {
37         if (resizeTimeout) {
38             clearTimeout(resizeTimeout);
39         }
40         resizeTimeout = setTimeout(function() {
41             var contentWidth = $('.content').width();
42             $('.docblock.short').width(function() {
43                 return contentWidth - 40 - $(this).prev().width();
44             }).addClass('nowrap');
45         }, 150);
46     }
47     resizeShortBlocks();
48     $(window).on('resize', resizeShortBlocks);
49
50     function highlightSourceLines() {
51         var i, from, to, match = window.location.hash.match(/^#?(\d+)(?:-(\d+))?$/);
52         if (match) {
53             from = parseInt(match[1], 10);
54             to = Math.min(50000, parseInt(match[2] || match[1], 10));
55             from = Math.min(from, to);
56             if ($('#' + from).length === 0) {
57                 return;
58             }
59             $('#' + from)[0].scrollIntoView();
60             $('.line-numbers span').removeClass('line-highlighted');
61             for (i = from; i <= to; i += 1) {
62                 $('#' + i).addClass('line-highlighted');
63             }
64         }
65     }
66     highlightSourceLines();
67     $(window).on('hashchange', highlightSourceLines);
68
69     $(document).on('keyup', function(e) {
70         if (document.activeElement.tagName === 'INPUT') {
71             return;
72         }
73
74         if (e.keyCode === 188 && $('#help').hasClass('hidden')) { // question mark
75             e.preventDefault();
76             $('#help').removeClass('hidden');
77         } else if (e.keyCode === 27) { // esc
78             if (!$('#help').hasClass('hidden')) {
79                 e.preventDefault();
80                 $('#help').addClass('hidden');
81             } else if (!$('#search').hasClass('hidden')) {
82                 e.preventDefault();
83                 $('#search').addClass('hidden');
84                 $('#main').removeClass('hidden');
85             }
86         } else if (e.keyCode === 83) { // S
87             e.preventDefault();
88             $('.search-input').focus();
89         }
90     }).on('click', function(e) {
91         if (!$(e.target).closest('#help').length) {
92             $('#help').addClass('hidden');
93         }
94     });
95
96     $('.version-selector').on('change', function() {
97         var i, match,
98             url = document.location.href,
99             stripped = '',
100             len = rootPath.match(/\.\.\//g).length + 1;
101
102         for (i = 0; i < len; i += 1) {
103             match = url.match(/\/[^\/]*$/);
104             if (i < len - 1) {
105                 stripped = match[0] + stripped;
106             }
107             url = url.substring(0, url.length - match[0].length);
108         }
109
110         url += '/' + $('.version-selector').val() + stripped;
111
112         document.location.href = url;
113     });
114
115     function initSearch(rawSearchIndex) {
116         var currentResults, index, searchIndex;
117         var params = getQueryStringParams();
118
119         // Populate search bar with query string search term when provided,
120         // but only if the input bar is empty. This avoid the obnoxious issue
121         // where you start trying to do a search, and the index loads, and
122         // suddenly your search is gone!
123         if ($(".search-input")[0].value === "") {
124             $(".search-input")[0].value = params.search || '';
125         }
126
127         /**
128          * Executes the query and builds an index of results
129          * @param  {[Object]} query     [The user query]
130          * @param  {[type]} max         [The maximum results returned]
131          * @param  {[type]} searchWords [The list of search words to query
132          *                               against]
133          * @return {[type]}             [A search index of results]
134          */
135         function execQuery(query, max, searchWords) {
136             var valLower = query.query.toLowerCase(),
137                 val = valLower,
138                 typeFilter = itemTypeFromName(query.type),
139                 results = [],
140                 split = valLower.split("::");
141
142             //remove empty keywords
143             for (var j = 0; j < split.length; j++) {
144                 split[j].toLowerCase();
145                 if (split[j] === "") {
146                     split.splice(j, 1);
147                 }
148             }
149
150             // quoted values mean literal search
151             var nSearchWords = searchWords.length;
152             if ((val.charAt(0) === "\"" || val.charAt(0) === "'") &&
153                 val.charAt(val.length - 1) === val.charAt(0))
154             {
155                 val = val.substr(1, val.length - 2);
156                 for (var i = 0; i < nSearchWords; i += 1) {
157                     if (searchWords[i] === val) {
158                         // filter type: ... queries
159                         if (typeFilter < 0 || typeFilter === searchIndex[i].ty) {
160                             results.push({id: i, index: -1});
161                         }
162                     }
163                     if (results.length === max) {
164                         break;
165                     }
166                 }
167             } else {
168                 // gather matching search results up to a certain maximum
169                 val = val.replace(/\_/g, "");
170                 for (var i = 0; i < split.length; i++) {
171                     for (var j = 0; j < nSearchWords; j += 1) {
172                         if (searchWords[j].indexOf(split[i]) > -1 ||
173                             searchWords[j].indexOf(val) > -1 ||
174                             searchWords[j].replace(/_/g, "").indexOf(val) > -1)
175                         {
176                             // filter type: ... queries
177                             if (typeFilter < 0 || typeFilter === searchIndex[j].ty) {
178                                 results.push({id: j, index: searchWords[j].replace(/_/g, "").indexOf(val)});
179                             }
180                         }
181                         if (results.length === max) {
182                             break;
183                         }
184                     }
185                 }
186             }
187
188             var nresults = results.length;
189             for (var i = 0; i < nresults; i += 1) {
190                 results[i].word = searchWords[results[i].id];
191                 results[i].item = searchIndex[results[i].id] || {};
192             }
193             // if there are no results then return to default and fail
194             if (results.length === 0) {
195                 return [];
196             }
197
198             results.sort(function(aaa, bbb) {
199                 var a, b;
200
201                 // sort by crate (non-current crate goes later)
202                 a = (aaa.item.crate !== window.currentCrate);
203                 b = (bbb.item.crate !== window.currentCrate);
204                 if (a !== b) return a - b;
205
206                 // sort by exact match (mismatch goes later)
207                 a = (aaa.word !== valLower);
208                 b = (bbb.word !== valLower);
209                 if (a !== b) return a - b;
210
211                 // sort by item name length (longer goes later)
212                 a = aaa.word.length;
213                 b = bbb.word.length;
214                 if (a !== b) return a - b;
215
216                 // sort by item name (lexicographically larger goes later)
217                 a = aaa.word;
218                 b = bbb.word;
219                 if (a !== b) return (a > b ? +1 : -1);
220
221                 // sort by index of keyword in item name (no literal occurrence goes later)
222                 a = (aaa.index < 0);
223                 b = (bbb.index < 0);
224                 if (a !== b) return a - b;
225                 // (later literal occurrence, if any, goes later)
226                 a = aaa.index;
227                 b = bbb.index;
228                 if (a !== b) return a - b;
229
230                 // sort by description (no description goes later)
231                 a = (aaa.item.desc === '');
232                 b = (bbb.item.desc === '');
233                 if (a !== b) return a - b;
234
235                 // sort by type (later occurrence in `itemTypes` goes later)
236                 a = aaa.item.ty;
237                 b = bbb.item.ty;
238                 if (a !== b) return a - b;
239
240                 // sort by path (lexicographically larger goes later)
241                 a = aaa.item.path;
242                 b = bbb.item.path;
243                 if (a !== b) return (a > b ? +1 : -1);
244
245                 // que sera, sera
246                 return 0;
247             });
248
249             // remove duplicates, according to the data provided
250             for (var i = results.length - 1; i > 0; i -= 1) {
251                 if (results[i].word === results[i - 1].word &&
252                     results[i].item.ty === results[i - 1].item.ty &&
253                     results[i].item.path === results[i - 1].item.path)
254                 {
255                     results[i].id = -1;
256                 }
257             }
258             for (var i = 0; i < results.length; i++) {
259                 var result = results[i],
260                     name = result.item.name.toLowerCase(),
261                     path = result.item.path.toLowerCase(),
262                     parent = result.item.parent;
263
264                 var valid = validateResult(name, path, split, parent);
265                 if (!valid) {
266                     result.id = -1;
267                 }
268             }
269             return results;
270         }
271
272         /**
273          * Validate performs the following boolean logic. For example:
274          * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
275          * exists in (name || path || parent) OR => ("file" && "open") exists in
276          * (name || path )
277          *
278          * This could be written functionally, but I wanted to minimise
279          * functions on stack.
280          *
281          * @param  {[string]} name   [The name of the result]
282          * @param  {[string]} path   [The path of the result]
283          * @param  {[string]} keys   [The keys to be used (["file", "open"])]
284          * @param  {[object]} parent [The parent of the result]
285          * @return {[boolean]}       [Whether the result is valid or not]
286          */
287         function validateResult(name, path, keys, parent) {
288             //initially valid
289             var validate = true;
290             //if there is a parent, then validate against parent
291             if (parent !== undefined) {
292                 for (var i = 0; i < keys.length; i++) {
293                     // if previous keys are valid and current key is in the
294                     // path, name or parent
295                     if ((validate) &&
296                         (name.toLowerCase().indexOf(keys[i]) > -1 ||
297                          path.toLowerCase().indexOf(keys[i]) > -1 ||
298                          parent.name.toLowerCase().indexOf(keys[i]) > -1))
299                     {
300                         validate = true;
301                     } else {
302                         validate = false;
303                     }
304                 }
305             } else {
306                 for (var i = 0; i < keys.length; i++) {
307                     // if previous keys are valid and current key is in the
308                     // path, name
309                     if ((validate) &&
310                         (name.toLowerCase().indexOf(keys[i]) > -1 ||
311                          path.toLowerCase().indexOf(keys[i]) > -1))
312                     {
313                         validate = true;
314                     } else {
315                         validate = false;
316                     }
317                 }
318             }
319             return validate;
320         }
321
322         function getQuery() {
323             var matches, type, query = $('.search-input').val();
324
325             matches = query.match(/^(fn|mod|str(uct)?|enum|trait|t(ype)?d(ef)?)\s*:\s*/i);
326             if (matches) {
327                 type = matches[1].replace(/^td$/, 'typedef')
328                                  .replace(/^str$/, 'struct')
329                                  .replace(/^tdef$/, 'typedef')
330                                  .replace(/^typed$/, 'typedef');
331                 query = query.substring(matches[0].length);
332             }
333
334             return {
335                 query: query,
336                 type: type,
337                 id: query + type,
338             };
339         }
340
341         function initSearchNav() {
342             var hoverTimeout, $results = $('.search-results .result');
343
344             $results.on('click', function() {
345                 var dst = $(this).find('a')[0];
346                 if (window.location.pathname == dst.pathname) {
347                     $('#search').addClass('hidden');
348                     $('#main').removeClass('hidden');
349                 }
350                 document.location.href = dst.href;
351             }).on('mouseover', function() {
352                 var $el = $(this);
353                 clearTimeout(hoverTimeout);
354                 hoverTimeout = setTimeout(function() {
355                     $results.removeClass('highlighted');
356                     $el.addClass('highlighted');
357                 }, 20);
358             });
359
360             $(document).off('keypress.searchnav');
361             $(document).on('keypress.searchnav', function(e) {
362                 var $active = $results.filter('.highlighted');
363
364                 if (e.keyCode === 38) { // up
365                     e.preventDefault();
366                     if (!$active.length || !$active.prev()) {
367                         return;
368                     }
369
370                     $active.prev().addClass('highlighted');
371                     $active.removeClass('highlighted');
372                 } else if (e.keyCode === 40) { // down
373                     e.preventDefault();
374                     if (!$active.length) {
375                         $results.first().addClass('highlighted');
376                     } else if ($active.next().length) {
377                         $active.next().addClass('highlighted');
378                         $active.removeClass('highlighted');
379                     }
380                 } else if (e.keyCode === 13) { // return
381                     e.preventDefault();
382                     if ($active.length) {
383                         document.location.href = $active.find('a').prop('href');
384                     }
385                 }
386             });
387         }
388
389         function escape(content) {
390             return $('<h1/>').text(content).html();
391         }
392
393         function showResults(results) {
394             var output, shown, query = getQuery();
395
396             currentResults = query.id;
397             output = '<h1>Results for ' + escape(query.query) +
398                 (query.type ? ' (type: ' + escape(query.type) + ')' : '') + '</h1>';
399             output += '<table class="search-results">';
400
401             if (results.length > 0) {
402                 shown = [];
403
404                 results.forEach(function(item) {
405                     var name, type;
406
407                     if (shown.indexOf(item) !== -1) {
408                         return;
409                     }
410
411                     shown.push(item);
412                     name = item.name;
413                     type = itemTypes[item.ty];
414
415                     output += '<tr class="' + type + ' result"><td>';
416
417                     if (type === 'mod') {
418                         output += item.path +
419                             '::<a href="' + rootPath +
420                             item.path.replace(/::/g, '/') + '/' +
421                             name + '/index.html" class="' +
422                             type + '">' + name + '</a>';
423                     } else if (type === 'static' || type === 'reexport') {
424                         output += item.path +
425                             '::<a href="' + rootPath +
426                             item.path.replace(/::/g, '/') +
427                             '/index.html" class="' + type +
428                             '">' + name + '</a>';
429                     } else if (item.parent !== undefined) {
430                         var myparent = item.parent;
431                         var anchor = '#' + type + '.' + name;
432                         output += item.path + '::' + myparent.name +
433                             '::<a href="' + rootPath +
434                             item.path.replace(/::/g, '/') +
435                             '/' + itemTypes[myparent.ty] +
436                             '.' + myparent.name +
437                             '.html' + anchor +
438                             '" class="' + type +
439                             '">' + name + '</a>';
440                     } else {
441                         output += item.path +
442                             '::<a href="' + rootPath +
443                             item.path.replace(/::/g, '/') +
444                             '/' + type +
445                             '.' + name +
446                             '.html" class="' + type +
447                             '">' + name + '</a>';
448                     }
449
450                     output += '</td><td><span class="desc">' + item.desc +
451                         '</span></td></tr>';
452                 });
453             } else {
454                 output += 'No results :( <a href="https://duckduckgo.com/?q=' +
455                     encodeURIComponent('rust ' + query.query) +
456                     '">Try on DuckDuckGo?</a>';
457             }
458
459             output += "</p>";
460             $('#main.content').addClass('hidden');
461             $('#search.content').removeClass('hidden').html(output);
462             $('#search .desc').width($('#search').width() - 40 -
463                 $('#search td:first-child').first().width());
464             initSearchNav();
465         }
466
467         function search(e) {
468             var query,
469                 filterdata = [],
470                 obj, i, len,
471                 results = [],
472                 maxResults = 200,
473                 resultIndex;
474             var params = getQueryStringParams();
475
476             query = getQuery();
477             if (e) {
478                 e.preventDefault();
479             }
480
481             if (!query.query || query.id === currentResults) {
482                 return;
483             }
484
485             // Because searching is incremental by character, only the most
486             // recent search query is added to the browser history.
487             if (browserSupportsHistoryApi()) {
488                 if (!history.state && !params.search) {
489                     history.pushState(query, "", "?search=" +
490                                                 encodeURIComponent(query.query));
491                 } else {
492                     history.replaceState(query, "", "?search=" +
493                                                 encodeURIComponent(query.query));
494                 }
495             }
496
497             resultIndex = execQuery(query, 20000, index);
498             len = resultIndex.length;
499             for (i = 0; i < len; i += 1) {
500                 if (resultIndex[i].id > -1) {
501                     obj = searchIndex[resultIndex[i].id];
502                     filterdata.push([obj.name, obj.ty, obj.path, obj.desc]);
503                     results.push(obj);
504                 }
505                 if (results.length >= maxResults) {
506                     break;
507                 }
508             }
509
510             showResults(results);
511         }
512
513         // This mapping table should match the discriminants of
514         // `rustdoc::html::item_type::ItemType` type in Rust.
515         var itemTypes = ["mod",
516                          "struct",
517                          "type",
518                          "fn",
519                          "type",
520                          "static",
521                          "trait",
522                          "impl",
523                          "viewitem",
524                          "tymethod",
525                          "method",
526                          "structfield",
527                          "variant",
528                          "ffi",
529                          "ffs",
530                          "macro",
531                          "primitive"];
532
533         function itemTypeFromName(typename) {
534             for (var i = 0; i < itemTypes.length; ++i) {
535                 if (itemTypes[i] === typename) return i;
536             }
537             return -1;
538         }
539
540         function buildIndex(rawSearchIndex) {
541             searchIndex = [];
542             var searchWords = [];
543             for (var crate in rawSearchIndex) {
544                 if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
545
546                 // an array of [(Number) item type,
547                 //              (String) name,
548                 //              (String) full path or empty string for previous path,
549                 //              (String) description,
550                 //              (optional Number) the parent path index to `paths`]
551                 var items = rawSearchIndex[crate].items;
552                 // an array of [(Number) item type,
553                 //              (String) name]
554                 var paths = rawSearchIndex[crate].paths;
555
556                 // convert `paths` into an object form
557                 var len = paths.length;
558                 for (var i = 0; i < len; ++i) {
559                     paths[i] = {ty: paths[i][0], name: paths[i][1]};
560                 }
561
562                 // convert `items` into an object form, and construct word indices.
563                 //
564                 // before any analysis is performed lets gather the search terms to
565                 // search against apart from the rest of the data.  This is a quick
566                 // operation that is cached for the life of the page state so that
567                 // all other search operations have access to this cached data for
568                 // faster analysis operations
569                 var len = items.length;
570                 var lastPath = "";
571                 for (var i = 0; i < len; i += 1) {
572                     var rawRow = items[i];
573                     var row = {crate: crate, ty: rawRow[0], name: rawRow[1],
574                                path: rawRow[2] || lastPath, desc: rawRow[3],
575                                parent: paths[rawRow[4]]};
576                     searchIndex.push(row);
577                     if (typeof row.name === "string") {
578                         var word = row.name.toLowerCase();
579                         searchWords.push(word);
580                     } else {
581                         searchWords.push("");
582                     }
583                     lastPath = row.path;
584                 }
585             }
586             return searchWords;
587         }
588
589         function startSearch() {
590             var keyUpTimeout;
591             $('.do-search').on('click', search);
592             $('.search-input').on('keyup', function() {
593                 clearTimeout(keyUpTimeout);
594                 keyUpTimeout = setTimeout(search, 100);
595             });
596
597             // Push and pop states are used to add search results to the browser
598             // history.
599             if (browserSupportsHistoryApi()) {
600                 $(window).on('popstate', function(e) {
601                     var params = getQueryStringParams();
602                     // When browsing back from search results the main page
603                     // visibility must be reset.
604                     if (!params.search) {
605                         $('#main.content').removeClass('hidden');
606                         $('#search.content').addClass('hidden');
607                     }
608                     // When browsing forward to search results the previous
609                     // search will be repeated, so the currentResults are
610                     // cleared to ensure the search is successful.
611                     currentResults = null;
612                     // Synchronize search bar with query string state and
613                     // perform the search. This will empty the bar if there's
614                     // nothing there, which lets you really go back to a
615                     // previous state with nothing in the bar.
616                     $('.search-input').val(params.search);
617                     // Some browsers fire 'onpopstate' for every page load
618                     // (Chrome), while others fire the event only when actually
619                     // popping a state (Firefox), which is why search() is
620                     // called both here and at the end of the startSearch()
621                     // function.
622                     search();
623                 });
624             }
625             search();
626         }
627
628         index = buildIndex(rawSearchIndex);
629         startSearch();
630
631         // Draw a convenient sidebar of known crates if we have a listing
632         if (rootPath == '../') {
633             var sidebar = $('.sidebar');
634             var div = $('<div>').attr('class', 'block crate');
635             div.append($('<h2>').text('Crates'));
636
637             var crates = [];
638             for (var crate in rawSearchIndex) {
639                 if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
640                 crates.push(crate);
641             }
642             crates.sort();
643             for (var i = 0; i < crates.length; i++) {
644                 var klass = 'crate';
645                 if (crates[i] == window.currentCrate) {
646                     klass += ' current';
647                 }
648                 div.append($('<a>', {'href': '../' + crates[i] + '/index.html',
649                                     'class': klass}).text(crates[i]));
650             }
651             sidebar.append(div);
652         }
653     }
654
655     window.initSearch = initSearch;
656
657     window.register_implementors = function(imp) {
658         var list = $('#implementors-list');
659         var libs = Object.getOwnPropertyNames(imp);
660         for (var i = 0; i < libs.length; i++) {
661             if (libs[i] == currentCrate) continue;
662             var structs = imp[libs[i]];
663             for (var j = 0; j < structs.length; j++) {
664                 var code = $('<code>').append(structs[j]);
665                 $.each(code.find('a'), function(idx, a) {
666                     var href = $(a).attr('href');
667                     if (!href.startsWith('http')) {
668                         $(a).attr('href', rootPath + $(a).attr('href'));
669                     }
670                 });
671                 var li = $('<li>').append(code);
672                 list.append(li);
673             }
674         }
675     };
676     if (window.pending_implementors) {
677         window.register_implementors(window.pending_implementors);
678     }
679
680     // See documentation in html/render.rs for what this is doing.
681     var query = getQueryStringParams();
682     if (query['gotosrc']) {
683         window.location = $('#src-' + query['gotosrc']).attr('href');
684     }
685 }());