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