Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rustdoc search by type #23289

Merged
merged 1 commit into from
Mar 15, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 107 additions & 1 deletion src/librustdoc/html/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
//! both occur before the crate is rendered.
pub use self::ExternalLocation::*;

use std::ascii::OwnedAsciiExt;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
Expand Down Expand Up @@ -246,6 +247,51 @@ struct IndexItem {
path: String,
desc: String,
parent: Option<ast::DefId>,
search_type: Option<IndexItemFunctionType>,
}

/// A type used for the search index.
struct Type {
name: Option<String>,
}

impl fmt::Display for Type {
/// Formats type as {name: $name}.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Wrapping struct fmt should never call us when self.name is None,
// but just to be safe we write `null` in that case.
match self.name {
Some(ref n) => write!(f, "{{\"name\":\"{}\"}}", n),
None => write!(f, "null")
}
}
}

/// Full type of functions/methods in the search index.
struct IndexItemFunctionType {
inputs: Vec<Type>,
output: Option<Type>
}

impl fmt::Display for IndexItemFunctionType {
/// Formats a full fn type as a JSON {inputs: [Type], outputs: Type/null}.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// If we couldn't figure out a type, just write `null`.
if self.inputs.iter().any(|ref i| i.name.is_none()) ||
(self.output.is_some() && self.output.as_ref().unwrap().name.is_none()) {
return write!(f, "null")
}

let inputs: Vec<String> = self.inputs.iter().map(|ref t| format!("{}", t)).collect();
try!(write!(f, "{{\"inputs\":[{}],\"output\":", inputs.connect(",")));

match self.output {
Some(ref t) => try!(write!(f, "{}", t)),
None => try!(write!(f, "null"))
};

Ok(try!(write!(f, "}}")))
}
}

// TLS keys used to carry information around during rendering.
Expand Down Expand Up @@ -413,6 +459,7 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> old_io::IoResult<Stri
path: fqp[..fqp.len() - 1].connect("::"),
desc: shorter(item.doc_value()).to_string(),
parent: Some(did),
search_type: None,
});
},
None => {}
Expand Down Expand Up @@ -462,7 +509,11 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> old_io::IoResult<Stri
let pathid = *nodeid_to_pathid.get(&nodeid).unwrap();
try!(write!(&mut w, ",{}", pathid));
}
None => {}
None => try!(write!(&mut w, ",null"))
}
match item.search_type {
Some(ref t) => try!(write!(&mut w, ",{}", t)),
None => try!(write!(&mut w, ",null"))
}
try!(write!(&mut w, "]"));
}
Expand Down Expand Up @@ -877,12 +928,21 @@ impl DocFolder for Cache {

match parent {
(parent, Some(path)) if is_method || (!self.privmod && !hidden_field) => {
// Needed to determine `self` type.
let parent_basename = self.parent_stack.first().and_then(|parent| {
match self.paths.get(parent) {
Some(&(ref fqp, _)) => Some(fqp[fqp.len() - 1].clone()),
_ => None
}
});

self.search_index.push(IndexItem {
ty: shortty(&item),
name: s.to_string(),
path: path.connect("::").to_string(),
desc: shorter(item.doc_value()).to_string(),
parent: parent,
search_type: get_index_search_type(&item, parent_basename),
});
}
(Some(parent), None) if is_method || (!self.privmod && !hidden_field)=> {
Expand Down Expand Up @@ -2304,6 +2364,52 @@ fn make_item_keywords(it: &clean::Item) -> String {
format!("{}, {}", get_basic_keywords(), it.name.as_ref().unwrap())
}

fn get_index_search_type(item: &clean::Item,
parent: Option<String>) -> Option<IndexItemFunctionType> {
let decl = match item.inner {
clean::FunctionItem(ref f) => &f.decl,
clean::MethodItem(ref m) => &m.decl,
clean::TyMethodItem(ref m) => &m.decl,
_ => return None
};

let mut inputs = Vec::new();

// Consider `self` an argument as well.
if let Some(name) = parent {
inputs.push(Type { name: Some(name.into_ascii_lowercase()) });
}

inputs.extend(&mut decl.inputs.values.iter().map(|arg| {
get_index_type(&arg.type_)
}));

let output = match decl.output {
clean::FunctionRetTy::Return(ref return_type) => Some(get_index_type(return_type)),
_ => None
};

Some(IndexItemFunctionType { inputs: inputs, output: output })
}

fn get_index_type(clean_type: &clean::Type) -> Type {
Type { name: get_index_type_name(clean_type).map(|s| s.into_ascii_lowercase()) }
}

fn get_index_type_name(clean_type: &clean::Type) -> Option<String> {
match *clean_type {
clean::ResolvedPath { ref path, .. } => {
let segments = &path.segments;
Some(segments[segments.len() - 1].name.clone())
},
clean::Generic(ref s) => Some(s.clone()),
clean::Primitive(ref p) => Some(format!("{:?}", p)),
clean::BorrowedRef { ref type_, .. } => get_index_type_name(type_),
// FIXME: add all from clean::Type.
_ => None
}
}

pub fn cache() -> Arc<Cache> {
CACHE_KEY.with(|c| c.borrow().clone())
}
37 changes: 35 additions & 2 deletions src/librustdoc/html/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,33 @@
break;
}
}
// searching by type
} else if (val.search("->") > -1) {
var trimmer = function (s) { return s.trim(); };
var parts = val.split("->").map(trimmer);
var input = parts[0];
// sort inputs so that order does not matter
var inputs = input.split(",").map(trimmer).sort();
var output = parts[1];

for (var i = 0; i < nSearchWords; ++i) {
var type = searchIndex[i].type;
if (!type) {
continue;
}

// sort index inputs so that order does not matter
var typeInputs = type.inputs.map(function (input) {
return input.name;
}).sort();

// allow searching for void (no output) functions as well
var typeOutput = type.output ? type.output.name : "";
if (inputs.toString() === typeInputs.toString() &&
output == typeOutput) {
results.push({id: i, index: -1, dontValidate: true});
}
}
} else {
// gather matching search results up to a certain maximum
val = val.replace(/\_/g, "");
Expand Down Expand Up @@ -325,6 +352,11 @@
path = result.item.path.toLowerCase(),
parent = result.item.parent;

// this validation does not make sense when searching by types
if (result.dontValidate) {
continue;
}

var valid = validateResult(name, path, split, parent);
if (!valid) {
result.id = -1;
Expand Down Expand Up @@ -590,7 +622,8 @@
// (String) name,
// (String) full path or empty string for previous path,
// (String) description,
// (optional Number) the parent path index to `paths`]
// (Number | null) the parent path index to `paths`]
// (Object | null) the type of the function (if any)
var items = rawSearchIndex[crate].items;
// an array of [(Number) item type,
// (String) name]
Expand All @@ -615,7 +648,7 @@
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]]};
parent: paths[rawRow[4]], type: rawRow[5]};
searchIndex.push(row);
if (typeof row.name === "string") {
var word = row.name.toLowerCase();
Expand Down