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