rustdoc: Implement cross-crate searching

A major discoverability issue with rustdoc is that all crates have their
documentation built in isolation, so it's difficult when looking at the
documentation for libstd to learn that there's a libcollections crate with a
HashMap in it.

This commit moves rustdoc a little closer to improving the multiple crate
experience. This unifies all search indexes for all crates into one file so all
pages share the same search index. This allows searching to work across crates
in the same documentation directory (as the standard distribution is currently
built).

This strategy involves updating a shared file amongst many rustdoc processes, so
I implemented a simple file locking API for handling synchronization for updates
to the shared files.

cc #12554
This commit is contained in:
Alex Crichton 2014-03-16 01:08:56 -07:00
parent d717d613e3
commit 848f7b734e
6 changed files with 382 additions and 156 deletions

191
src/librustdoc/flock.rs Normal file
View File

@ -0,0 +1,191 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! Simple file-locking apis for each OS.
//!
//! This is not meant to be in the standard library, it does nothing with
//! green/native threading. This is just a bare-bones enough solution for
//! librustdoc, it is not production quality at all.
#[allow(non_camel_case_types)];
pub use self::imp::Lock;
#[cfg(unix)]
mod imp {
use std::libc;
#[cfg(target_os = "linux")]
mod os {
use std::libc;
pub struct flock {
l_type: libc::c_short,
l_whence: libc::c_short,
l_start: libc::off_t,
l_len: libc::off_t,
l_pid: libc::pid_t,
// not actually here, but brings in line with freebsd
l_sysid: libc::c_int,
}
pub static F_WRLCK: libc::c_short = 1;
pub static F_UNLCK: libc::c_short = 2;
pub static F_SETLK: libc::c_int = 6;
pub static F_SETLKW: libc::c_int = 7;
}
#[cfg(target_os = "freebsd")]
mod os {
use std::libc;
pub struct flock {
l_start: libc::off_t,
l_len: libc::off_t,
l_pid: libc::pid_t,
l_type: libc::c_short,
l_whence: libc::c_short,
l_sysid: libc::c_int,
}
pub static F_UNLCK: libc::c_short = 2;
pub static F_WRLCK: libc::c_short = 3;
pub static F_SETLK: libc::c_int = 12;
pub static F_SETLKW: libc::c_int = 13;
}
#[cfg(target_os = "macos")]
mod os {
use std::libc;
pub struct flock {
l_start: libc::off_t,
l_len: libc::off_t,
l_pid: libc::pid_t,
l_type: libc::c_short,
l_whence: libc::c_short,
// not actually here, but brings in line with freebsd
l_sysid: libc::c_int,
}
pub static F_UNLCK: libc::c_short = 2;
pub static F_WRLCK: libc::c_short = 3;
pub static F_SETLK: libc::c_int = 8;
pub static F_SETLKW: libc::c_int = 9;
}
pub struct Lock {
priv fd: libc::c_int,
}
impl Lock {
pub fn new(p: &Path) -> Lock {
let fd = p.with_c_str(|s| unsafe {
libc::open(s, libc::O_RDWR | libc::O_CREAT, libc::S_IRWXU)
});
assert!(fd > 0);
let flock = os::flock {
l_start: 0,
l_len: 0,
l_pid: 0,
l_whence: libc::SEEK_SET as libc::c_short,
l_type: os::F_WRLCK,
l_sysid: 0,
};
let ret = unsafe {
libc::fcntl(fd, os::F_SETLKW, &flock as *os::flock)
};
if ret == -1 {
unsafe { libc::close(fd); }
fail!("could not lock `{}`", p.display())
}
Lock { fd: fd }
}
}
impl Drop for Lock {
fn drop(&mut self) {
let flock = os::flock {
l_start: 0,
l_len: 0,
l_pid: 0,
l_whence: libc::SEEK_SET as libc::c_short,
l_type: os::F_UNLCK,
l_sysid: 0,
};
unsafe {
libc::fcntl(self.fd, os::F_SETLK, &flock as *os::flock);
libc::close(self.fd);
}
}
}
}
#[cfg(windows)]
mod imp {
use std::libc;
use std::mem;
use std::os::win32::as_utf16_p;
use std::ptr;
static LOCKFILE_EXCLUSIVE_LOCK: libc::DWORD = 0x00000002;
extern "system" {
fn LockFileEx(hFile: libc::HANDLE,
dwFlags: libc::DWORD,
dwReserved: libc::DWORD,
nNumberOfBytesToLockLow: libc::DWORD,
nNumberOfBytesToLockHigh: libc::DWORD,
lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL;
fn UnlockFileEx(hFile: libc::HANDLE,
dwReserved: libc::DWORD,
nNumberOfBytesToLockLow: libc::DWORD,
nNumberOfBytesToLockHigh: libc::DWORD,
lpOverlapped: libc::LPOVERLAPPED) -> libc::BOOL;
}
pub struct Lock {
priv handle: libc::HANDLE,
}
impl Lock {
pub fn new(p: &Path) -> Lock {
let handle = as_utf16_p(p.as_str().unwrap(), |p| unsafe {
libc::CreateFileW(p, libc::GENERIC_READ, 0, ptr::mut_null(),
libc::CREATE_ALWAYS,
libc::FILE_ATTRIBUTE_NORMAL,
ptr::mut_null())
});
assert!(handle as uint != libc::INVALID_HANDLE_VALUE as uint);
let mut overlapped: libc::OVERLAPPED = unsafe { mem::init() };
let ret = unsafe {
LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK, 0, 100, 0,
&mut overlapped)
};
if ret == 0 {
unsafe { libc::CloseHandle(handle); }
fail!("could not lock `{}`", p.display())
}
Lock { handle: handle }
}
}
impl Drop for Lock {
fn drop(&mut self) {
let mut overlapped: libc::OVERLAPPED = unsafe { mem::init() };
unsafe {
UnlockFileEx(self.handle, 0, 100, 0, &mut overlapped);
libc::CloseHandle(self.handle);
}
}
}
}

View File

@ -37,7 +37,7 @@ pub fn render<T: fmt::Show, S: fmt::Show>(
<link href='http://fonts.googleapis.com/css?family=Oswald:700|Inconsolata:400,700' <link href='http://fonts.googleapis.com/css?family=Oswald:700|Inconsolata:400,700'
rel='stylesheet' type='text/css'> rel='stylesheet' type='text/css'>
<link rel=\"stylesheet\" type=\"text/css\" href=\"{root_path}{krate}/main.css\"> <link rel=\"stylesheet\" type=\"text/css\" href=\"{root_path}main.css\">
{favicon, select, none{} other{<link rel=\"shortcut icon\" href=\"#\" />}} {favicon, select, none{} other{<link rel=\"shortcut icon\" href=\"#\" />}}
</head> </head>
@ -74,13 +74,6 @@ pub fn render<T: fmt::Show, S: fmt::Show>(
<section class=\"footer\"></section> <section class=\"footer\"></section>
<script>
var rootPath = \"{root_path}\";
</script>
<script src=\"{root_path}{krate}/jquery.js\"></script>
<script src=\"{root_path}{krate}/search-index.js\"></script>
<script src=\"{root_path}{krate}/main.js\"></script>
<div id=\"help\" class=\"hidden\"> <div id=\"help\" class=\"hidden\">
<div class=\"shortcuts\"> <div class=\"shortcuts\">
<h1>Keyboard shortcuts</h1> <h1>Keyboard shortcuts</h1>
@ -111,6 +104,14 @@ pub fn render<T: fmt::Show, S: fmt::Show>(
</p> </p>
</div> </div>
</div> </div>
<script>
var rootPath = \"{root_path}\";
var currentCrate = \"{krate}\";
</script>
<script src=\"{root_path}jquery.js\"></script>
<script src=\"{root_path}main.js\"></script>
<script async src=\"{root_path}search-index.js\"></script>
</body> </body>
</html> </html>
", ",

View File

@ -36,7 +36,7 @@
use std::fmt; use std::fmt;
use std::local_data; use std::local_data;
use std::io; use std::io;
use std::io::{fs, File, BufferedWriter}; use std::io::{fs, File, BufferedWriter, MemWriter, BufferedReader};
use std::str; use std::str;
use std::vec; use std::vec;
use std::vec_ng::Vec; use std::vec_ng::Vec;
@ -283,48 +283,75 @@ pub fn run(mut krate: clean::Crate, dst: Path) -> io::IoResult<()> {
}; };
} }
// Add all the static files
let mut dst = cx.dst.join(krate.name.as_slice());
try!(mkdir(&dst));
try!(write(dst.join("jquery.js"),
include_str!("static/jquery-2.1.0.min.js")));
try!(write(dst.join("main.js"), include_str!("static/main.js")));
try!(write(dst.join("main.css"), include_str!("static/main.css")));
try!(write(dst.join("normalize.css"),
include_str!("static/normalize.css")));
// Publish the search index // Publish the search index
{ let index = {
dst.push("search-index.js"); let mut w = MemWriter::new();
let mut w = BufferedWriter::new(File::create(&dst).unwrap()); try!(write!(&mut w, "searchIndex['{}'] = [", krate.name));
let w = &mut w as &mut Writer;
try!(write!(w, "var searchIndex = ["));
for (i, item) in cache.search_index.iter().enumerate() { for (i, item) in cache.search_index.iter().enumerate() {
if i > 0 { if i > 0 {
try!(write!(w, ",")); try!(write!(&mut w, ","));
} }
try!(write!(w, "\\{ty:\"{}\",name:\"{}\",path:\"{}\",desc:{}", try!(write!(&mut w, "\\{ty:\"{}\",name:\"{}\",path:\"{}\",desc:{}",
item.ty, item.name, item.path, item.ty, item.name, item.path,
item.desc.to_json().to_str())); item.desc.to_json().to_str()));
match item.parent { match item.parent {
Some(id) => { Some(id) => {
try!(write!(w, ",parent:'{}'", id)); try!(write!(&mut w, ",parent:'{}'", id));
} }
None => {} None => {}
} }
try!(write!(w, "\\}")); try!(write!(&mut w, "\\}"));
} }
try!(write!(w, "];")); try!(write!(&mut w, "];"));
try!(write!(w, "var allPaths = \\{")); try!(write!(&mut w, "allPaths['{}'] = \\{", krate.name));
for (i, (&id, &(ref fqp, short))) in cache.paths.iter().enumerate() { for (i, (&id, &(ref fqp, short))) in cache.paths.iter().enumerate() {
if i > 0 { if i > 0 {
try!(write!(w, ",")); try!(write!(&mut w, ","));
} }
try!(write!(w, "'{}':\\{type:'{}',name:'{}'\\}", try!(write!(&mut w, "'{}':\\{type:'{}',name:'{}'\\}",
id, short, *fqp.last().unwrap())); id, short, *fqp.last().unwrap()));
} }
try!(write!(w, "\\};")); try!(write!(&mut w, "\\};"));
try!(w.flush());
str::from_utf8_owned(w.unwrap()).unwrap()
};
// Write out the shared files. Note that these are shared among all rustdoc
// docs placed in the output directory, so this needs to be a synchronized
// operation with respect to all other rustdocs running around.
{
try!(mkdir(&cx.dst));
let _lock = ::flock::Lock::new(&cx.dst.join(".lock"));
// Add all the static files. These may already exist, but we just
// overwrite them anyway to make sure that they're fresh and up-to-date.
try!(write(cx.dst.join("jquery.js"),
include_str!("static/jquery-2.1.0.min.js")));
try!(write(cx.dst.join("main.js"), include_str!("static/main.js")));
try!(write(cx.dst.join("main.css"), include_str!("static/main.css")));
try!(write(cx.dst.join("normalize.css"),
include_str!("static/normalize.css")));
// Update the search index
let dst = cx.dst.join("search-index.js");
let mut all_indexes = Vec::new();
all_indexes.push(index);
if dst.exists() {
for line in BufferedReader::new(File::open(&dst)).lines() {
let line = try!(line);
if !line.starts_with("searchIndex") { continue }
if line.starts_with(format!("searchIndex['{}']", krate.name)) {
continue
}
all_indexes.push(line);
}
}
let mut w = try!(File::create(&dst));
try!(writeln!(&mut w, r"var searchIndex = \{\}; var allPaths = \{\};"));
for index in all_indexes.iter() {
try!(writeln!(&mut w, "{}", *index));
}
try!(writeln!(&mut w, "initSearch(searchIndex);"));
} }
// Render all source files (this may turn into a giant no-op) // Render all source files (this may turn into a giant no-op)

View File

@ -9,7 +9,7 @@
// except according to those terms. // except according to those terms.
/*jslint browser: true, es5: true */ /*jslint browser: true, es5: true */
/*globals $: true, searchIndex: true, rootPath: true, allPaths: true */ /*globals $: true, rootPath: true, allPaths: true */
(function() { (function() {
"use strict"; "use strict";
@ -23,7 +23,8 @@
map(function(s) { map(function(s) {
var pair = s.split("="); var pair = s.split("=");
params[decodeURIComponent(pair[0])] = params[decodeURIComponent(pair[0])] =
typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]); typeof pair[1] === "undefined" ?
null : decodeURIComponent(pair[1]);
}); });
return params; return params;
} }
@ -111,8 +112,9 @@
document.location.href = url; document.location.href = url;
}); });
function initSearch(searchIndex) { function initSearch(rawSearchIndex) {
var currentResults, index, params = getQueryStringParams(); var currentResults, index, searchIndex;
var params = getQueryStringParams();
// Populate search bar with query string search term when provided, // Populate search bar with query string search term when provided,
// but only if the input bar is empty. This avoid the obnoxious issue // but only if the input bar is empty. This avoid the obnoxious issue
@ -126,7 +128,8 @@
* Executes the query and builds an index of results * Executes the query and builds an index of results
* @param {[Object]} query [The user query] * @param {[Object]} query [The user query]
* @param {[type]} max [The maximum results returned] * @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] * @return {[type]} [A search index of results]
*/ */
function execQuery(query, max, searchWords) { function execQuery(query, max, searchWords) {
@ -148,7 +151,9 @@
// quoted values mean literal search // quoted values mean literal search
bb = searchWords.length; bb = searchWords.length;
if ((val.charAt(0) === "\"" || val.charAt(0) === "'") && val.charAt(val.length - 1) === val.charAt(0)) { if ((val.charAt(0) === "\"" || val.charAt(0) === "'") &&
val.charAt(val.length - 1) === val.charAt(0))
{
val = val.substr(1, val.length - 2); val = val.substr(1, val.length - 2);
for (aa = 0; aa < bb; aa += 1) { for (aa = 0; aa < bb; aa += 1) {
if (searchWords[aa] === val) { if (searchWords[aa] === val) {
@ -166,7 +171,10 @@
val = val.replace(/\_/g, ""); val = val.replace(/\_/g, "");
for (var i = 0; i < split.length; i++) { for (var i = 0; i < split.length; i++) {
for (aa = 0; aa < bb; aa += 1) { 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) { if (searchWords[aa].indexOf(split[i]) > -1 ||
searchWords[aa].indexOf(val) > -1 ||
searchWords[aa].replace(/_/g, "").indexOf(val) > -1)
{
// filter type: ... queries // filter type: ... queries
if (!typeFilter || typeFilter === searchIndex[aa].ty) { if (!typeFilter || typeFilter === searchIndex[aa].ty) {
results.push([aa, searchWords[aa].replace(/_/g, "").indexOf(val)]); results.push([aa, searchWords[aa].replace(/_/g, "").indexOf(val)]);
@ -185,6 +193,7 @@
results[aa].push(searchIndex[results[aa][0]].path); results[aa].push(searchIndex[results[aa][0]].path);
results[aa].push(searchIndex[results[aa][0]].name); results[aa].push(searchIndex[results[aa][0]].name);
results[aa].push(searchIndex[results[aa][0]].parent); results[aa].push(searchIndex[results[aa][0]].parent);
results[aa].push(searchIndex[results[aa][0]].crate);
} }
// if there are no results then return to default and fail // if there are no results then return to default and fail
if (results.length === 0) { if (results.length === 0) {
@ -193,7 +202,8 @@
// sort by exact match // sort by exact match
results.sort(function search_complete_sort0(aaa, bbb) { results.sort(function search_complete_sort0(aaa, bbb) {
if (searchWords[aaa[0]] === valLower && searchWords[bbb[0]] !== valLower) { if (searchWords[aaa[0]] === valLower &&
searchWords[bbb[0]] !== valLower) {
return 1; return 1;
} }
}); });
@ -207,7 +217,8 @@
// second sorting attempt // second sorting attempt
// sort by item name // sort by item name
results.sort(function search_complete_sort1(aaa, bbb) { results.sort(function search_complete_sort1(aaa, bbb) {
if (searchWords[aaa[0]].length === searchWords[bbb[0]].length && searchWords[aaa[0]] > searchWords[bbb[0]]) { if (searchWords[aaa[0]].length === searchWords[bbb[0]].length &&
searchWords[aaa[0]] > searchWords[bbb[0]]) {
return 1; return 1;
} }
}); });
@ -223,21 +234,26 @@
// fourth sorting attempt // fourth sorting attempt
// sort by type // sort by type
results.sort(function search_complete_sort3(aaa, bbb) { results.sort(function search_complete_sort3(aaa, bbb) {
if (searchWords[aaa[0]] === searchWords[bbb[0]] && aaa[2] > bbb[2]) { if (searchWords[aaa[0]] === searchWords[bbb[0]] &&
aaa[2] > bbb[2]) {
return 1; return 1;
} }
}); });
// fifth sorting attempt // fifth sorting attempt
// sort by path // sort by path
results.sort(function search_complete_sort4(aaa, bbb) { results.sort(function search_complete_sort4(aaa, bbb) {
if (searchWords[aaa[0]] === searchWords[bbb[0]] && aaa[2] === bbb[2] && aaa[3] > bbb[3]) { if (searchWords[aaa[0]] === searchWords[bbb[0]] &&
aaa[2] === bbb[2] && aaa[3] > bbb[3]) {
return 1; return 1;
} }
}); });
// sixth sorting attempt // sixth sorting attempt
// remove duplicates, according to the data provided // remove duplicates, according to the data provided
for (aa = results.length - 1; aa > 0; aa -= 1) { 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]) { 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; results[aa][0] = -1;
} }
} }
@ -245,7 +261,7 @@
var result = results[i], var result = results[i],
name = result[4].toLowerCase(), name = result[4].toLowerCase(),
path = result[3].toLowerCase(), path = result[3].toLowerCase(),
parent = allPaths[result[5]]; parent = allPaths[result[6]][result[5]];
var valid = validateResult(name, path, split, parent); var valid = validateResult(name, path, split, parent);
if (!valid) { if (!valid) {
@ -256,11 +272,14 @@
} }
/** /**
* Validate performs the following boolean logic. For example: "File::open" will give * Validate performs the following boolean logic. For example:
* IF A PARENT EXISTS => ("file" && "open") exists in (name || path || parent) * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
* OR => ("file" && "open") exists in (name || path ) * 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]} name [The name of the result]
* @param {[string]} path [The path of the result] * @param {[string]} path [The path of the result]
* @param {[string]} keys [The keys to be used (["file", "open"])] * @param {[string]} keys [The keys to be used (["file", "open"])]
@ -273,8 +292,13 @@
//if there is a parent, then validate against parent //if there is a parent, then validate against parent
if (parent !== undefined) { if (parent !== undefined) {
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
// if previous keys are valid and current key is in the path, name or parent // if previous keys are valid and current key is in the
if ((validate) && (name.toLowerCase().indexOf(keys[i]) > -1 || path.toLowerCase().indexOf(keys[i]) > -1 || parent.name.toLowerCase().indexOf(keys[i]) > -1)) { // 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; validate = true;
} else { } else {
validate = false; validate = false;
@ -282,8 +306,12 @@
} }
} else { } else {
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
// if previous keys are valid and current key is in the path, name // if previous keys are valid and current key is in the
if ((validate) && (name.toLowerCase().indexOf(keys[i]) > -1 || path.toLowerCase().indexOf(keys[i]) > -1)) { // path, name
if ((validate) &&
(name.toLowerCase().indexOf(keys[i]) > -1 ||
path.toLowerCase().indexOf(keys[i]) > -1))
{
validate = true; validate = true;
} else { } else {
validate = false; validate = false;
@ -298,7 +326,10 @@
matches = query.match(/^(fn|mod|str(uct)?|enum|trait|t(ype)?d(ef)?)\s*:\s*/i); matches = query.match(/^(fn|mod|str(uct)?|enum|trait|t(ype)?d(ef)?)\s*:\s*/i);
if (matches) { 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); query = query.substring(matches[0].length);
} }
@ -314,7 +345,6 @@
$results.on('click', function() { $results.on('click', function() {
var dst = $(this).find('a')[0]; var dst = $(this).find('a')[0];
console.log(window.location.pathname, dst.pathname);
if (window.location.pathname == dst.pathname) { if (window.location.pathname == dst.pathname) {
$('#search').addClass('hidden'); $('#search').addClass('hidden');
$('#main').removeClass('hidden'); $('#main').removeClass('hidden');
@ -362,7 +392,8 @@
var output, shown, query = getQuery(); var output, shown, query = getQuery();
currentResults = query.id; currentResults = query.id;
output = '<h1>Results for ' + query.query + (query.type ? ' (type: ' + query.type + ')' : '') + '</h1>'; output = '<h1>Results for ' + query.query +
(query.type ? ' (type: ' + query.type + ')' : '') + '</h1>';
output += '<table class="search-results">'; output += '<table class="search-results">';
if (results.length > 0) { if (results.length > 0) {
@ -394,7 +425,7 @@
'/index.html" class="' + type + '/index.html" class="' + type +
'">' + name + '</a>'; '">' + name + '</a>';
} else if (item.parent !== undefined) { } else if (item.parent !== undefined) {
var myparent = allPaths[item.parent]; var myparent = allPaths[item.crate][item.parent];
var anchor = '#' + type + '.' + name; var anchor = '#' + type + '.' + name;
output += item.path + '::' + myparent.name + output += item.path + '::' + myparent.name +
'::<a href="' + rootPath + '::<a href="' + rootPath +
@ -449,13 +480,15 @@
return; return;
} }
// Because searching is incremental by character, only the most recent search query // Because searching is incremental by character, only the most
// is added to the browser history. // recent search query is added to the browser history.
if (browserSupportsHistoryApi()) { if (browserSupportsHistoryApi()) {
if (!history.state && !params.search) { if (!history.state && !params.search) {
history.pushState(query, "", "?search=" + encodeURIComponent(query.query)); history.pushState(query, "", "?search=" +
encodeURIComponent(query.query));
} else { } else {
history.replaceState(query, "", "?search=" + encodeURIComponent(query.query)); history.replaceState(query, "", "?search=" +
encodeURIComponent(query.query));
} }
} }
@ -472,91 +505,33 @@
} }
} }
// 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); showResults(results);
} }
function buildIndex(searchIndex) { function buildIndex(rawSearchIndex) {
var len = searchIndex.length, searchIndex = [];
i = 0, var searchWords = [];
searchWords = []; for (var crate in rawSearchIndex) {
if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
var len = rawSearchIndex[crate].length;
var i = 0;
// before any analysis is performed lets gather the search terms to // before any analysis is performed lets gather the search terms to
// search against apart from the rest of the data. This is a quick // 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 // operation that is cached for the life of the page state so that
// all other search operations have access to this cached data for // all other search operations have access to this cached data for
// faster analysis operations // faster analysis operations
for (i = 0; i < len; i += 1) { for (i = 0; i < len; i += 1) {
if (typeof searchIndex[i].name === "string") { rawSearchIndex[crate][i].crate = crate;
searchWords.push(searchIndex[i].name.toLowerCase()); searchIndex.push(rawSearchIndex[crate][i]);
} else { if (typeof rawSearchIndex[crate][i].name === "string") {
searchWords.push(""); var word = rawSearchIndex[crate][i].name.toLowerCase();
searchWords.push(word);
} else {
searchWords.push("");
}
} }
} }
return searchWords; return searchWords;
} }
@ -567,17 +542,21 @@
clearTimeout(keyUpTimeout); clearTimeout(keyUpTimeout);
keyUpTimeout = setTimeout(search, 100); keyUpTimeout = setTimeout(search, 100);
}); });
// Push and pop states are used to add search results to the browser history.
// Push and pop states are used to add search results to the browser
// history.
if (browserSupportsHistoryApi()) { if (browserSupportsHistoryApi()) {
$(window).on('popstate', function(e) { $(window).on('popstate', function(e) {
var params = getQueryStringParams(); var params = getQueryStringParams();
// When browsing back from search results the main page visibility must be reset. // When browsing back from search results the main page
// visibility must be reset.
if (!params.search) { if (!params.search) {
$('#main.content').removeClass('hidden'); $('#main.content').removeClass('hidden');
$('#search.content').addClass('hidden'); $('#search.content').addClass('hidden');
} }
// When browsing forward to search results the previous search will be repeated, // When browsing forward to search results the previous
// so the currentResults are cleared to ensure the search is successful. // search will be repeated, so the currentResults are
// cleared to ensure the search is successful.
currentResults = null; currentResults = null;
// Synchronize search bar with query string state and // Synchronize search bar with query string state and
// perform the search, but don't empty the bar if there's // perform the search, but don't empty the bar if there's
@ -585,19 +564,46 @@
if (params.search !== undefined) { if (params.search !== undefined) {
$('.search-input').val(params.search); $('.search-input').val(params.search);
} }
// Some browsers fire 'onpopstate' for every page load (Chrome), while others fire the // Some browsers fire 'onpopstate' for every page load
// event only when actually popping a state (Firefox), which is why search() is called // (Chrome), while others fire the event only when actually
// both here and at the end of the startSearch() function. // popping a state (Firefox), which is why search() is
// called both here and at the end of the startSearch()
// function.
search(); search();
}); });
} }
search(); search();
} }
index = buildIndex(searchIndex); index = buildIndex(rawSearchIndex);
startSearch(); startSearch();
// Draw a convenient sidebar of known crates if we have a listing
if (rootPath == '../') {
console.log('here');
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]));
div.append($('<br>'));
}
sidebar.append(div);
}
} }
initSearch(searchIndex); window.initSearch = initSearch;
}()); }());

View File

@ -52,6 +52,7 @@ pub mod passes;
pub mod plugins; pub mod plugins;
pub mod visit_ast; pub mod visit_ast;
pub mod test; pub mod test;
mod flock;
pub static SCHEMA_VERSION: &'static str = "0.8.1"; pub static SCHEMA_VERSION: &'static str = "0.8.1";

View File

@ -3643,7 +3643,7 @@ pub mod funcs {
pub fn open(path: *c_char, oflag: c_int, mode: c_int) pub fn open(path: *c_char, oflag: c_int, mode: c_int)
-> c_int; -> c_int;
pub fn creat(path: *c_char, mode: mode_t) -> c_int; pub fn creat(path: *c_char, mode: mode_t) -> c_int;
pub fn fcntl(fd: c_int, cmd: c_int) -> c_int; pub fn fcntl(fd: c_int, cmd: c_int, ...) -> c_int;
} }
} }