diff options
| author | bors <bors@rust-lang.org> | 2015-03-14 22:07:25 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2015-03-14 22:07:25 +0000 |
| commit | 30e1f9a1c2bf7134135800bc9afd082773defadc (patch) | |
| tree | 4be916100bcfe847155d684079aa92cac5af49af | |
| parent | 3400c9ed9f1200a59f6452db5c71af2182db29a5 (diff) | |
| parent | 7b7b938be1123631fed4e021fd3ef9562cf81b0c (diff) | |
| download | rust-30e1f9a1c2bf7134135800bc9afd082773defadc.tar.gz rust-30e1f9a1c2bf7134135800bc9afd082773defadc.zip | |
Auto merge of #23289 - mihneadb:rustdoc-search-by-type, r=alexcrichton
This adds search by type (for functions/methods) support to Rustdoc. Target issue is at https://github.com/rust-lang/rfcs/issues/658.
I've described my approach here: https://github.com/rust-lang/rfcs/issues/658#issuecomment-76484200. I'll copy the text in here as well:
---
Hi, it took me longer than I wished, but I have implemented this in a not-too-complex way that I think can be extended to support more complex features (like the ones mentioned [here](https://github.com/rust-lang/rust/issues/12866#issuecomment-66945317)).
The idea is to generate a JSON representation of the types of methods/functions in the existing index, and then make the JS understand when it should look by type (and not by name).
I tried to come up with a JSON representation that can be extended to support generics, bounds, ref/mut annotations and so on. Here are a few samples:
Function:
```rust
fn to_uppercase(c: char) -> char
```
```json
{
"inputs": [
{"name": "char"}
],
"output": {
"name": "char",
}
}
```
Method (implemented or defined in trait):
```rust
// in struct Vec
// self is considered an argument as well
fn capacity(&self) -> usize
```
```json
{
"inputs": [
{"name": "vec"}
],
"output": {
"name": "usize"
}
}
```
This simple format can be extended by adding more fields, like `generic: bool`, a `bounds` mapping and so on.
I have a working implementation in https://github.com/rust-lang/rust/compare/master...mihneadb:rustdoc-search-by-type. You can check out a live demo [here](http://data.mihneadb.net/doc/std/index.html?search=charext%20-%3E%20char).

The feature list is not that long:
- search by types (you *can* use generics as well, as long as you use the exact name - e.g. [`vec,t -> `](http://data.mihneadb.net/doc/std/index.html?search=vec%2C%20t%20-%3E))
- order of arguments does not matter
- `self` is took into account as well (e.g. search for `vec -> usize`)
- does not use "complex" annotations (e.g. you don't search for `&char -> char` but for `char -> char`)
My goal is to get a working, minimal "base" merged so that others can build upon it. How should I proceed? Do I open a PR (badly in need of code review since this is my first non "hello world"-ish rust code)?
---
| -rw-r--r-- | src/librustdoc/html/render.rs | 108 | ||||
| -rw-r--r-- | src/librustdoc/html/static/main.js | 37 |
2 files changed, 142 insertions, 3 deletions
diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs index dba7b16ecee..4c6341efb72 100644 --- a/src/librustdoc/html/render.rs +++ b/src/librustdoc/html/render.rs @@ -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}; @@ -239,6 +240,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. @@ -409,6 +455,7 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> io::Result<String> { path: fqp[..fqp.len() - 1].connect("::"), desc: shorter(item.doc_value()).to_string(), parent: Some(did), + search_type: None, }); }, None => {} @@ -458,7 +505,11 @@ fn build_index(krate: &clean::Crate, cache: &mut Cache) -> io::Result<String> { 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, "]")); } @@ -872,12 +923,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)=> { @@ -2307,6 +2367,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()) } diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js index 9fa8cf7941e..ca6d9441957 100644 --- a/src/librustdoc/html/static/main.js +++ b/src/librustdoc/html/static/main.js @@ -209,6 +209,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, ""); @@ -329,6 +356,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; @@ -573,7 +605,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] @@ -598,7 +631,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(); |
