// except according to those terms.
/*jslint browser: true, es5: true */
-/*globals $: true, searchIndex: true, rootPath: true, allPaths: true */
+/*globals $: true, rootPath: true */
(function() {
"use strict";
$('.js-only').removeClass('js-only');
+ function getQueryStringParams() {
+ var params = {};
+ window.location.search.substring(1).split("&").
+ map(function(s) {
+ var pair = s.split("=");
+ params[decodeURIComponent(pair[0])] =
+ typeof pair[1] === "undefined" ?
+ null : decodeURIComponent(pair[1]);
+ });
+ return params;
+ }
+
+ function browserSupportsHistoryApi() {
+ return window.history && typeof window.history.pushState === "function";
+ }
+
function resizeShortBlocks() {
if (resizeTimeout) {
clearTimeout(resizeTimeout);
document.location.href = url;
});
- function initSearch(searchIndex) {
- var currentResults, index;
+ function initSearch(rawSearchIndex) {
+ var currentResults, index, searchIndex;
+ var params = getQueryStringParams();
- // clear cached values from the search bar
- $(".search-input")[0].value = '';
+ // Populate search bar with query string search term when provided,
+ // but only if the input bar is empty. This avoid the obnoxious issue
+ // where you start trying to do a search, and the index loads, and
+ // suddenly your search is gone!
+ if ($(".search-input")[0].value === "") {
+ $(".search-input")[0].value = params.search || '';
+ }
/**
* Executes the query and builds an index of results
* @param {[Object]} query [The user query]
* @param {[type]} max [The maximum results returned]
- * @param {[type]} searchWords [The list of search words to query against]
+ * @param {[type]} searchWords [The list of search words to query
+ * against]
* @return {[type]} [A search index of results]
*/
function execQuery(query, max, searchWords) {
var valLower = query.query.toLowerCase(),
val = valLower,
- typeFilter = query.type,
+ typeFilter = itemTypeFromName(query.type),
results = [],
- aa = 0,
- bb = 0,
split = valLower.split("::");
//remove empty keywords
}
// quoted values mean literal search
- bb = searchWords.length;
- if ((val.charAt(0) === "\"" || val.charAt(0) === "'") && val.charAt(val.length - 1) === val.charAt(0)) {
+ var nSearchWords = searchWords.length;
+ if ((val.charAt(0) === "\"" || val.charAt(0) === "'") &&
+ val.charAt(val.length - 1) === val.charAt(0))
+ {
val = val.substr(1, val.length - 2);
- for (aa = 0; aa < bb; aa += 1) {
- if (searchWords[aa] === val) {
+ for (var i = 0; i < nSearchWords; i += 1) {
+ if (searchWords[i] === val) {
// filter type: ... queries
- if (!typeFilter || typeFilter === searchIndex[aa].ty) {
- results.push([aa, -1]);
+ if (typeFilter < 0 || typeFilter === searchIndex[i].ty) {
+ results.push({id: i, index: -1});
}
}
if (results.length === max) {
// gather matching search results up to a certain maximum
val = val.replace(/\_/g, "");
for (var i = 0; i < split.length; i++) {
- for (aa = 0; aa < bb; aa += 1) {
- if (searchWords[aa].indexOf(split[i]) > -1 || searchWords[aa].indexOf(val) > -1 || searchWords[aa].replace(/_/g, "").indexOf(val) > -1) {
+ for (var j = 0; j < nSearchWords; j += 1) {
+ if (searchWords[j].indexOf(split[i]) > -1 ||
+ searchWords[j].indexOf(val) > -1 ||
+ searchWords[j].replace(/_/g, "").indexOf(val) > -1)
+ {
// filter type: ... queries
- if (!typeFilter || typeFilter === searchIndex[aa].ty) {
- results.push([aa, searchWords[aa].replace(/_/g, "").indexOf(val)]);
+ if (typeFilter < 0 || typeFilter === searchIndex[j].ty) {
+ results.push({id: j, index: searchWords[j].replace(/_/g, "").indexOf(val)});
}
}
if (results.length === max) {
}
}
- bb = results.length;
- for (aa = 0; aa < bb; aa += 1) {
- results[aa].push(searchIndex[results[aa][0]].ty);
- results[aa].push(searchIndex[results[aa][0]].path);
- results[aa].push(searchIndex[results[aa][0]].name);
- results[aa].push(searchIndex[results[aa][0]].parent);
+ var nresults = results.length;
+ for (var i = 0; i < nresults; i += 1) {
+ results[i].word = searchWords[results[i].id];
+ results[i].item = searchIndex[results[i].id] || {};
}
// if there are no results then return to default and fail
if (results.length === 0) {
return [];
}
- // sort by exact match
- results.sort(function search_complete_sort0(aaa, bbb) {
- if (searchWords[aaa[0]] === valLower && searchWords[bbb[0]] !== valLower) {
- return 1;
- }
+ results.sort(function(aaa, bbb) {
+ var a, b;
+
+ // sort by crate (non-current crate goes later)
+ a = (aaa.item.crate !== window.currentCrate);
+ b = (bbb.item.crate !== window.currentCrate);
+ if (a !== b) return a - b;
+
+ // sort by exact match (mismatch goes later)
+ a = (aaa.word !== valLower);
+ b = (bbb.word !== valLower);
+ if (a !== b) return a - b;
+
+ // sort by item name length (longer goes later)
+ a = aaa.word.length;
+ b = bbb.word.length;
+ if (a !== b) return a - b;
+
+ // sort by item name (lexicographically larger goes later)
+ a = aaa.word;
+ b = bbb.word;
+ if (a !== b) return (a > b ? +1 : -1);
+
+ // sort by index of keyword in item name (no literal occurrence goes later)
+ a = (aaa.index < 0);
+ b = (bbb.index < 0);
+ if (a !== b) return a - b;
+ // (later literal occurrence, if any, goes later)
+ a = aaa.index;
+ b = bbb.index;
+ if (a !== b) return a - b;
+
+ // sort by description (no description goes later)
+ a = (aaa.item.desc === '');
+ b = (bbb.item.desc === '');
+ if (a !== b) return a - b;
+
+ // sort by type (later occurrence in `itemTypes` goes later)
+ a = aaa.item.ty;
+ b = bbb.item.ty;
+ if (a !== b) return a - b;
+
+ // sort by path (lexicographically larger goes later)
+ a = aaa.item.path;
+ b = bbb.item.path;
+ if (a !== b) return (a > b ? +1 : -1);
+
+ // que sera, sera
+ return 0;
});
- // first sorting attempt
- // sort by item name length
- results.sort(function search_complete_sort1(aaa, bbb) {
- if (searchWords[aaa[0]].length > searchWords[bbb[0]].length) {
- return 1;
- }
- });
- // second sorting attempt
- // sort by item name
- results.sort(function search_complete_sort1(aaa, bbb) {
- if (searchWords[aaa[0]].length === searchWords[bbb[0]].length && searchWords[aaa[0]] > searchWords[bbb[0]]) {
- return 1;
- }
- });
- // third sorting attempt
- // sort by index of keyword in item name
- if (results[0][1] !== -1) {
- results.sort(function search_complete_sort1(aaa, bbb) {
- if (aaa[1] > bbb[1] && bbb[1] === 0) {
- return 1;
- }
- });
- }
- // fourth sorting attempt
- // sort by type
- results.sort(function search_complete_sort3(aaa, bbb) {
- if (searchWords[aaa[0]] === searchWords[bbb[0]] && aaa[2] > bbb[2]) {
- return 1;
- }
- });
- // fifth sorting attempt
- // sort by path
- results.sort(function search_complete_sort4(aaa, bbb) {
- if (searchWords[aaa[0]] === searchWords[bbb[0]] && aaa[2] === bbb[2] && aaa[3] > bbb[3]) {
- return 1;
- }
- });
- // sixth sorting attempt
+
// remove duplicates, according to the data provided
- for (aa = results.length - 1; aa > 0; aa -= 1) {
- if (searchWords[results[aa][0]] === searchWords[results[aa - 1][0]] && results[aa][2] === results[aa - 1][2] && results[aa][3] === results[aa - 1][3]) {
- results[aa][0] = -1;
+ for (var i = results.length - 1; i > 0; i -= 1) {
+ if (results[i].word === results[i - 1].word &&
+ results[i].item.ty === results[i - 1].item.ty &&
+ results[i].item.path === results[i - 1].item.path)
+ {
+ results[i].id = -1;
}
}
for (var i = 0; i < results.length; i++) {
var result = results[i],
- name = result[4].toLowerCase(),
- path = result[3].toLowerCase(),
- parent = allPaths[result[5]];
+ name = result.item.name.toLowerCase(),
+ path = result.item.path.toLowerCase(),
+ parent = result.item.parent;
var valid = validateResult(name, path, split, parent);
if (!valid) {
- result[0] = -1;
+ result.id = -1;
}
}
return results;
}
/**
- * Validate performs the following boolean logic. For example: "File::open" will give
- * IF A PARENT EXISTS => ("file" && "open") exists in (name || path || parent)
- * OR => ("file" && "open") exists in (name || path )
+ * Validate performs the following boolean logic. For example:
+ * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
+ * exists in (name || path || parent) OR => ("file" && "open") exists in
+ * (name || path )
+ *
+ * This could be written functionally, but I wanted to minimise
+ * functions on stack.
*
- * This could be written functionally, but I wanted to minimise functions on stack.
* @param {[string]} name [The name of the result]
* @param {[string]} path [The path of the result]
* @param {[string]} keys [The keys to be used (["file", "open"])]
//if there is a parent, then validate against parent
if (parent !== undefined) {
for (var i = 0; i < keys.length; i++) {
- // if previous keys are valid and current key is in the path, name or parent
- if ((validate) && (name.toLowerCase().indexOf(keys[i]) > -1 || path.toLowerCase().indexOf(keys[i]) > -1 || parent.name.toLowerCase().indexOf(keys[i]) > -1)) {
+ // if previous keys are valid and current key is in the
+ // path, name or parent
+ if ((validate) &&
+ (name.toLowerCase().indexOf(keys[i]) > -1 ||
+ path.toLowerCase().indexOf(keys[i]) > -1 ||
+ parent.name.toLowerCase().indexOf(keys[i]) > -1))
+ {
validate = true;
} else {
validate = false;
}
} else {
for (var i = 0; i < keys.length; i++) {
- // if previous keys are valid and current key is in the path, name
- if ((validate) && (name.toLowerCase().indexOf(keys[i]) > -1 || path.toLowerCase().indexOf(keys[i]) > -1)) {
+ // if previous keys are valid and current key is in the
+ // path, name
+ if ((validate) &&
+ (name.toLowerCase().indexOf(keys[i]) > -1 ||
+ path.toLowerCase().indexOf(keys[i]) > -1))
+ {
validate = true;
} else {
validate = false;
matches = query.match(/^(fn|mod|str(uct)?|enum|trait|t(ype)?d(ef)?)\s*:\s*/i);
if (matches) {
- type = matches[1].replace(/^td$/, 'typedef').replace(/^str$/, 'struct').replace(/^tdef$/, 'typedef').replace(/^typed$/, 'typedef');
+ type = matches[1].replace(/^td$/, 'typedef')
+ .replace(/^str$/, 'struct')
+ .replace(/^tdef$/, 'typedef')
+ .replace(/^typed$/, 'typedef');
query = query.substring(matches[0].length);
}
$results.on('click', function() {
var dst = $(this).find('a')[0];
- console.log(window.location.pathname, dst.pathname);
if (window.location.pathname == dst.pathname) {
$('#search').addClass('hidden');
$('#main').removeClass('hidden');
});
}
+ function escape(content) {
+ return $('<h1/>').text(content).html();
+ }
+
function showResults(results) {
var output, shown, query = getQuery();
currentResults = query.id;
- output = '<h1>Results for ' + query.query + (query.type ? ' (type: ' + query.type + ')' : '') + '</h1>';
+ output = '<h1>Results for ' + escape(query.query) +
+ (query.type ? ' (type: ' + escape(query.type) + ')' : '') + '</h1>';
output += '<table class="search-results">';
if (results.length > 0) {
shown.push(item);
name = item.name;
- type = item.ty;
+ type = itemTypes[item.ty];
output += '<tr class="' + type + ' result"><td>';
'/index.html" class="' + type +
'">' + name + '</a>';
} else if (item.parent !== undefined) {
- var myparent = allPaths[item.parent];
+ var myparent = item.parent;
var anchor = '#' + type + '.' + name;
output += item.path + '::' + myparent.name +
'::<a href="' + rootPath +
item.path.replace(/::/g, '/') +
- '/' + myparent.type +
+ '/' + itemTypes[myparent.ty] +
'.' + myparent.name +
'.html' + anchor +
'" class="' + type +
results = [],
maxResults = 200,
resultIndex;
+ var params = getQueryStringParams();
query = getQuery();
if (e) {
return;
}
+ // Because searching is incremental by character, only the most
+ // recent search query is added to the browser history.
+ if (browserSupportsHistoryApi()) {
+ if (!history.state && !params.search) {
+ history.pushState(query, "", "?search=" +
+ encodeURIComponent(query.query));
+ } else {
+ history.replaceState(query, "", "?search=" +
+ encodeURIComponent(query.query));
+ }
+ }
+
resultIndex = execQuery(query, 20000, index);
len = resultIndex.length;
for (i = 0; i < len; i += 1) {
- if (resultIndex[i][0] > -1) {
- obj = searchIndex[resultIndex[i][0]];
+ if (resultIndex[i].id > -1) {
+ obj = searchIndex[resultIndex[i].id];
filterdata.push([obj.name, obj.ty, obj.path, obj.desc]);
results.push(obj);
}
}
}
- // TODO add sorting capability through this function?
- //
- // // the handler for the table heading filtering
- // filterdraw = function search_complete_filterdraw(node) {
- // var name = "",
- // arrow = "",
- // op = 0,
- // tbody = node.parentNode.parentNode.nextSibling,
- // anchora = {},
- // tra = {},
- // tha = {},
- // td1a = {},
- // td2a = {},
- // td3a = {},
- // aaa = 0,
- // bbb = 0;
- //
- // // the 4 following conditions set the rules for each
- // // table heading
- // if (node === ths[0]) {
- // op = 0;
- // name = "name";
- // ths[1].innerHTML = ths[1].innerHTML.split(" ")[0];
- // ths[2].innerHTML = ths[2].innerHTML.split(" ")[0];
- // ths[3].innerHTML = ths[3].innerHTML.split(" ")[0];
- // }
- // if (node === ths[1]) {
- // op = 1;
- // name = "type";
- // ths[0].innerHTML = ths[0].innerHTML.split(" ")[0];
- // ths[2].innerHTML = ths[2].innerHTML.split(" ")[0];
- // ths[3].innerHTML = ths[3].innerHTML.split(" ")[0];
- // }
- // if (node === ths[2]) {
- // op = 2;
- // name = "path";
- // ths[0].innerHTML = ths[0].innerHTML.split(" ")[0];
- // ths[1].innerHTML = ths[1].innerHTML.split(" ")[0];
- // ths[3].innerHTML = ths[3].innerHTML.split(" ")[0];
- // }
- // if (node === ths[3]) {
- // op = 3;
- // name = "description";
- // ths[0].innerHTML = ths[0].innerHTML.split(" ")[0];
- // ths[1].innerHTML = ths[1].innerHTML.split(" ")[0];
- // ths[2].innerHTML = ths[2].innerHTML.split(" ")[0];
- // }
- //
- // // ascending or descending search
- // arrow = node.innerHTML.split(" ")[1];
- // if (arrow === undefined || arrow === "\u25b2") {
- // arrow = "\u25bc";
- // } else {
- // arrow = "\u25b2";
- // }
- //
- // // filter the data
- // filterdata.sort(function search_complete_filterDraw_sort(xx, yy) {
- // if ((arrow === "\u25b2" && xx[op].toLowerCase() < yy[op].toLowerCase()) || (arrow === "\u25bc" && xx[op].toLowerCase() > yy[op].toLowerCase())) {
- // return 1;
- // }
- // });
- // };
-
showResults(results);
}
- function buildIndex(searchIndex) {
- var len = searchIndex.length,
- i = 0,
- searchWords = [];
+ // This mapping table should match the discriminants of
+ // `rustdoc::html::item_type::ItemType` type in Rust.
+ var itemTypes = ["mod",
+ "struct",
+ "type",
+ "fn",
+ "type",
+ "static",
+ "trait",
+ "impl",
+ "viewitem",
+ "tymethod",
+ "method",
+ "structfield",
+ "variant",
+ "ffi",
+ "ffs",
+ "macro",
+ "primitive"];
+
+ function itemTypeFromName(typename) {
+ for (var i = 0; i < itemTypes.length; ++i) {
+ if (itemTypes[i] === typename) return i;
+ }
+ return -1;
+ }
- // before any analysis is performed lets gather the search terms to
- // search against apart from the rest of the data. This is a quick
- // operation that is cached for the life of the page state so that
- // all other search operations have access to this cached data for
- // faster analysis operations
- for (i = 0; i < len; i += 1) {
- if (typeof searchIndex[i].name === "string") {
- searchWords.push(searchIndex[i].name.toLowerCase());
- } else {
- searchWords.push("");
+ function buildIndex(rawSearchIndex) {
+ searchIndex = [];
+ var searchWords = [];
+ for (var crate in rawSearchIndex) {
+ if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
+
+ // an array of [(Number) item type,
+ // (String) name,
+ // (String) full path or empty string for previous path,
+ // (String) description,
+ // (optional Number) the parent path index to `paths`]
+ var items = rawSearchIndex[crate].items;
+ // an array of [(Number) item type,
+ // (String) name]
+ var paths = rawSearchIndex[crate].paths;
+
+ // convert `paths` into an object form
+ var len = paths.length;
+ for (var i = 0; i < len; ++i) {
+ paths[i] = {ty: paths[i][0], name: paths[i][1]};
}
- }
+ // convert `items` into an object form, and construct word indices.
+ //
+ // before any analysis is performed lets gather the search terms to
+ // search against apart from the rest of the data. This is a quick
+ // operation that is cached for the life of the page state so that
+ // all other search operations have access to this cached data for
+ // faster analysis operations
+ var len = items.length;
+ var lastPath = "";
+ for (var i = 0; i < len; i += 1) {
+ var rawRow = items[i];
+ var row = {crate: crate, ty: rawRow[0], name: rawRow[1],
+ path: rawRow[2] || lastPath, desc: rawRow[3],
+ parent: paths[rawRow[4]]};
+ searchIndex.push(row);
+ if (typeof row.name === "string") {
+ var word = row.name.toLowerCase();
+ searchWords.push(word);
+ } else {
+ searchWords.push("");
+ }
+ lastPath = row.path;
+ }
+ }
return searchWords;
}
clearTimeout(keyUpTimeout);
keyUpTimeout = setTimeout(search, 100);
});
+
+ // Push and pop states are used to add search results to the browser
+ // history.
+ if (browserSupportsHistoryApi()) {
+ $(window).on('popstate', function(e) {
+ var params = getQueryStringParams();
+ // When browsing back from search results the main page
+ // visibility must be reset.
+ if (!params.search) {
+ $('#main.content').removeClass('hidden');
+ $('#search.content').addClass('hidden');
+ }
+ // When browsing forward to search results the previous
+ // search will be repeated, so the currentResults are
+ // cleared to ensure the search is successful.
+ currentResults = null;
+ // Synchronize search bar with query string state and
+ // perform the search. This will empty the bar if there's
+ // nothing there, which lets you really go back to a
+ // previous state with nothing in the bar.
+ $('.search-input').val(params.search);
+ // Some browsers fire 'onpopstate' for every page load
+ // (Chrome), while others fire the event only when actually
+ // popping a state (Firefox), which is why search() is
+ // called both here and at the end of the startSearch()
+ // function.
+ search();
+ });
+ }
+ search();
}
- index = buildIndex(searchIndex);
+ index = buildIndex(rawSearchIndex);
startSearch();
+
+ // Draw a convenient sidebar of known crates if we have a listing
+ if (rootPath == '../') {
+ var sidebar = $('.sidebar');
+ var div = $('<div>').attr('class', 'block crate');
+ div.append($('<h2>').text('Crates'));
+
+ var crates = [];
+ for (var crate in rawSearchIndex) {
+ if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
+ crates.push(crate);
+ }
+ crates.sort();
+ for (var i = 0; i < crates.length; i++) {
+ var klass = 'crate';
+ if (crates[i] == window.currentCrate) {
+ klass += ' current';
+ }
+ div.append($('<a>', {'href': '../' + crates[i] + '/index.html',
+ 'class': klass}).text(crates[i]));
+ }
+ sidebar.append(div);
+ }
}
- initSearch(searchIndex);
+ window.initSearch = initSearch;
+
+ window.register_implementors = function(imp) {
+ var list = $('#implementors-list');
+ var libs = Object.getOwnPropertyNames(imp);
+ for (var i = 0; i < libs.length; i++) {
+ if (libs[i] == currentCrate) continue;
+ var structs = imp[libs[i]];
+ for (var j = 0; j < structs.length; j++) {
+ var code = $('<code>').append(structs[j]);
+ $.each(code.find('a'), function(idx, a) {
+ var href = $(a).attr('href');
+ if (!href.startsWith('http')) {
+ $(a).attr('href', rootPath + $(a).attr('href'));
+ }
+ });
+ var li = $('<li>').append(code);
+ list.append(li);
+ }
+ }
+ };
+ if (window.pending_implementors) {
+ window.register_implementors(window.pending_implementors);
+ }
+
+ // See documentation in html/render.rs for what this is doing.
+ var query = getQueryStringParams();
+ if (query['gotosrc']) {
+ window.location = $('#src-' + query['gotosrc']).attr('href');
+ }
}());