Skip to content

Commit 7b7b938

Browse files
committed
Add support to search functions by type to rustdoc.
1 parent 880fb89 commit 7b7b938

File tree

2 files changed

+142
-3
lines changed

2 files changed

+142
-3
lines changed

src/librustdoc/html/render.rs

+107-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
//! both occur before the crate is rendered.
3535
pub use self::ExternalLocation::*;
3636

37+
use std::ascii::OwnedAsciiExt;
3738
use std::cell::RefCell;
3839
use std::cmp::Ordering;
3940
use std::collections::{HashMap, HashSet};
@@ -246,6 +247,51 @@ struct IndexItem {
246247
path: String,
247248
desc: String,
248249
parent: Option<ast::DefId>,
250+
search_type: Option<IndexItemFunctionType>,
251+
}
252+
253+
/// A type used for the search index.
254+
struct Type {
255+
name: Option<String>,
256+
}
257+
258+
impl fmt::Display for Type {
259+
/// Formats type as {name: $name}.
260+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
261+
// Wrapping struct fmt should never call us when self.name is None,
262+
// but just to be safe we write `null` in that case.
263+
match self.name {
264+
Some(ref n) => write!(f, "{{\"name\":\"{}\"}}", n),
265+
None => write!(f, "null")
266+
}
267+
}
268+
}
269+
270+
/// Full type of functions/methods in the search index.
271+
struct IndexItemFunctionType {
272+
inputs: Vec<Type>,
273+
output: Option<Type>
274+
}
275+
276+
impl fmt::Display for IndexItemFunctionType {
277+
/// Formats a full fn type as a JSON {inputs: [Type], outputs: Type/null}.
278+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279+
// If we couldn't figure out a type, just write `null`.
280+
if self.inputs.iter().any(|ref i| i.name.is_none()) ||
281+
(self.output.is_some() && self.output.as_ref().unwrap().name.is_none()) {
282+
return write!(f, "null")
283+
}
284+
285+
let inputs: Vec<String> = self.inputs.iter().map(|ref t| format!("{}", t)).collect();
286+
try!(write!(f, "{{\"inputs\":[{}],\"output\":", inputs.connect(",")));
287+
288+
match self.output {
289+
Some(ref t) => try!(write!(f, "{}", t)),
290+
None => try!(write!(f, "null"))
291+
};
292+
293+
Ok(try!(write!(f, "}}")))
294+
}
249295
}
250296

251297
// TLS keys used to carry information around during rendering.
@@ -413,6 +459,7 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> old_io::IoResult<Stri
413459
path: fqp[..fqp.len() - 1].connect("::"),
414460
desc: shorter(item.doc_value()).to_string(),
415461
parent: Some(did),
462+
search_type: None,
416463
});
417464
},
418465
None => {}
@@ -462,7 +509,11 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> old_io::IoResult<Stri
462509
let pathid = *nodeid_to_pathid.get(&nodeid).unwrap();
463510
try!(write!(&mut w, ",{}", pathid));
464511
}
465-
None => {}
512+
None => try!(write!(&mut w, ",null"))
513+
}
514+
match item.search_type {
515+
Some(ref t) => try!(write!(&mut w, ",{}", t)),
516+
None => try!(write!(&mut w, ",null"))
466517
}
467518
try!(write!(&mut w, "]"));
468519
}
@@ -877,12 +928,21 @@ impl DocFolder for Cache {
877928

878929
match parent {
879930
(parent, Some(path)) if is_method || (!self.privmod && !hidden_field) => {
931+
// Needed to determine `self` type.
932+
let parent_basename = self.parent_stack.first().and_then(|parent| {
933+
match self.paths.get(parent) {
934+
Some(&(ref fqp, _)) => Some(fqp[fqp.len() - 1].clone()),
935+
_ => None
936+
}
937+
});
938+
880939
self.search_index.push(IndexItem {
881940
ty: shortty(&item),
882941
name: s.to_string(),
883942
path: path.connect("::").to_string(),
884943
desc: shorter(item.doc_value()).to_string(),
885944
parent: parent,
945+
search_type: get_index_search_type(&item, parent_basename),
886946
});
887947
}
888948
(Some(parent), None) if is_method || (!self.privmod && !hidden_field)=> {
@@ -2304,6 +2364,52 @@ fn make_item_keywords(it: &clean::Item) -> String {
23042364
format!("{}, {}", get_basic_keywords(), it.name.as_ref().unwrap())
23052365
}
23062366

2367+
fn get_index_search_type(item: &clean::Item,
2368+
parent: Option<String>) -> Option<IndexItemFunctionType> {
2369+
let decl = match item.inner {
2370+
clean::FunctionItem(ref f) => &f.decl,
2371+
clean::MethodItem(ref m) => &m.decl,
2372+
clean::TyMethodItem(ref m) => &m.decl,
2373+
_ => return None
2374+
};
2375+
2376+
let mut inputs = Vec::new();
2377+
2378+
// Consider `self` an argument as well.
2379+
if let Some(name) = parent {
2380+
inputs.push(Type { name: Some(name.into_ascii_lowercase()) });
2381+
}
2382+
2383+
inputs.extend(&mut decl.inputs.values.iter().map(|arg| {
2384+
get_index_type(&arg.type_)
2385+
}));
2386+
2387+
let output = match decl.output {
2388+
clean::FunctionRetTy::Return(ref return_type) => Some(get_index_type(return_type)),
2389+
_ => None
2390+
};
2391+
2392+
Some(IndexItemFunctionType { inputs: inputs, output: output })
2393+
}
2394+
2395+
fn get_index_type(clean_type: &clean::Type) -> Type {
2396+
Type { name: get_index_type_name(clean_type).map(|s| s.into_ascii_lowercase()) }
2397+
}
2398+
2399+
fn get_index_type_name(clean_type: &clean::Type) -> Option<String> {
2400+
match *clean_type {
2401+
clean::ResolvedPath { ref path, .. } => {
2402+
let segments = &path.segments;
2403+
Some(segments[segments.len() - 1].name.clone())
2404+
},
2405+
clean::Generic(ref s) => Some(s.clone()),
2406+
clean::Primitive(ref p) => Some(format!("{:?}", p)),
2407+
clean::BorrowedRef { ref type_, .. } => get_index_type_name(type_),
2408+
// FIXME: add all from clean::Type.
2409+
_ => None
2410+
}
2411+
}
2412+
23072413
pub fn cache() -> Arc<Cache> {
23082414
CACHE_KEY.with(|c| c.borrow().clone())
23092415
}

src/librustdoc/html/static/main.js

+35-2
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,33 @@
205205
break;
206206
}
207207
}
208+
// searching by type
209+
} else if (val.search("->") > -1) {
210+
var trimmer = function (s) { return s.trim(); };
211+
var parts = val.split("->").map(trimmer);
212+
var input = parts[0];
213+
// sort inputs so that order does not matter
214+
var inputs = input.split(",").map(trimmer).sort();
215+
var output = parts[1];
216+
217+
for (var i = 0; i < nSearchWords; ++i) {
218+
var type = searchIndex[i].type;
219+
if (!type) {
220+
continue;
221+
}
222+
223+
// sort index inputs so that order does not matter
224+
var typeInputs = type.inputs.map(function (input) {
225+
return input.name;
226+
}).sort();
227+
228+
// allow searching for void (no output) functions as well
229+
var typeOutput = type.output ? type.output.name : "";
230+
if (inputs.toString() === typeInputs.toString() &&
231+
output == typeOutput) {
232+
results.push({id: i, index: -1, dontValidate: true});
233+
}
234+
}
208235
} else {
209236
// gather matching search results up to a certain maximum
210237
val = val.replace(/\_/g, "");
@@ -325,6 +352,11 @@
325352
path = result.item.path.toLowerCase(),
326353
parent = result.item.parent;
327354

355+
// this validation does not make sense when searching by types
356+
if (result.dontValidate) {
357+
continue;
358+
}
359+
328360
var valid = validateResult(name, path, split, parent);
329361
if (!valid) {
330362
result.id = -1;
@@ -590,7 +622,8 @@
590622
// (String) name,
591623
// (String) full path or empty string for previous path,
592624
// (String) description,
593-
// (optional Number) the parent path index to `paths`]
625+
// (Number | null) the parent path index to `paths`]
626+
// (Object | null) the type of the function (if any)
594627
var items = rawSearchIndex[crate].items;
595628
// an array of [(Number) item type,
596629
// (String) name]
@@ -615,7 +648,7 @@
615648
var rawRow = items[i];
616649
var row = {crate: crate, ty: rawRow[0], name: rawRow[1],
617650
path: rawRow[2] || lastPath, desc: rawRow[3],
618-
parent: paths[rawRow[4]]};
651+
parent: paths[rawRow[4]], type: rawRow[5]};
619652
searchIndex.push(row);
620653
if (typeof row.name === "string") {
621654
var word = row.name.toLowerCase();

0 commit comments

Comments
 (0)