Skip to content

Commit

Permalink
Add support to search functions by type to rustdoc.
Browse files Browse the repository at this point in the history
  • Loading branch information
mihneadb committed Mar 14, 2015
1 parent 880fb89 commit 7b7b938
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 3 deletions.
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

0 comments on commit 7b7b938

Please sign in to comment.