]>
Commit | Line | Data |
---|---|---|
c1a9b12d | 1 | // Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT |
1a4d82fc JJ |
2 | // file at the top-level directory of this distribution and at |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | //! Rustdoc's HTML Rendering module | |
12 | //! | |
13 | //! This modules contains the bulk of the logic necessary for rendering a | |
14 | //! rustdoc `clean::Crate` instance to a set of static HTML pages. This | |
15 | //! rendering process is largely driven by the `format!` syntax extension to | |
16 | //! perform all I/O into files and streams. | |
17 | //! | |
18 | //! The rendering process is largely driven by the `Context` and `Cache` | |
19 | //! structures. The cache is pre-populated by crawling the crate in question, | |
bd371182 | 20 | //! and then it is shared among the various rendering threads. The cache is meant |
1a4d82fc | 21 | //! to be a fairly large structure not implementing `Clone` (because it's shared |
bd371182 AL |
22 | //! among threads). The context, however, should be a lightweight structure. This |
23 | //! is cloned per-thread and contains information about what is currently being | |
1a4d82fc JJ |
24 | //! rendered. |
25 | //! | |
26 | //! In order to speed up rendering (mostly because of markdown rendering), the | |
27 | //! rendering process has been parallelized. This parallelization is only | |
28 | //! exposed through the `crate` method on the context, and then also from the | |
29 | //! fact that the shared cache is stored in TLS (and must be accessed as such). | |
30 | //! | |
31 | //! In addition to rendering the crate itself, this module is also responsible | |
32 | //! for creating the corresponding search index and source file renderings. | |
bd371182 | 33 | //! These threads are not parallelized (they haven't been a bottleneck yet), and |
1a4d82fc JJ |
34 | //! both occur before the crate is rendered. |
35 | pub use self::ExternalLocation::*; | |
36 | ||
ff7c6d11 | 37 | use std::borrow::Cow; |
1a4d82fc | 38 | use std::cell::RefCell; |
85aaf69f | 39 | use std::cmp::Ordering; |
abe05a73 | 40 | use std::collections::{BTreeMap, HashSet}; |
1a4d82fc | 41 | use std::default::Default; |
b039eaaf | 42 | use std::error; |
476ff2be | 43 | use std::fmt::{self, Display, Formatter, Write as FmtWrite}; |
9e0c209e | 44 | use std::fs::{self, File, OpenOptions}; |
c34b1796 AL |
45 | use std::io::prelude::*; |
46 | use std::io::{self, BufWriter, BufReader}; | |
1a4d82fc | 47 | use std::iter::repeat; |
9346a6ac | 48 | use std::mem; |
7453a54e | 49 | use std::path::{PathBuf, Path, Component}; |
1a4d82fc JJ |
50 | use std::str; |
51 | use std::sync::Arc; | |
52 | ||
53 | use externalfiles::ExternalHtml; | |
54 | ||
7453a54e | 55 | use serialize::json::{ToJson, Json, as_json}; |
476ff2be | 56 | use syntax::{abi, ast}; |
ff7c6d11 | 57 | use syntax::codemap::FileName; |
476ff2be | 58 | use rustc::hir::def_id::{CrateNum, CRATE_DEF_INDEX, DefId}; |
92a42be0 | 59 | use rustc::middle::privacy::AccessLevels; |
b039eaaf | 60 | use rustc::middle::stability; |
54a0048b | 61 | use rustc::hir; |
476ff2be | 62 | use rustc::util::nodemap::{FxHashMap, FxHashSet}; |
9e0c209e | 63 | use rustc_data_structures::flock; |
1a4d82fc | 64 | |
ea8adc8c | 65 | use clean::{self, AttributesExt, GetDefId, SelfTy, Mutability, Span}; |
1a4d82fc JJ |
66 | use doctree; |
67 | use fold::DocFolder; | |
d9579d0f | 68 | use html::escape::Escape; |
62682a34 | 69 | use html::format::{ConstnessSpace}; |
d9579d0f AL |
70 | use html::format::{TyParamBounds, WhereClause, href, AbiSpace}; |
71 | use html::format::{VisSpace, Method, UnsafetySpace, MutableSpace}; | |
a7813a04 | 72 | use html::format::fmt_impl_for_trait_page; |
1a4d82fc | 73 | use html::item_type::ItemType; |
cc61c64b | 74 | use html::markdown::{self, Markdown, MarkdownHtml, MarkdownSummaryLine, RenderType}; |
62682a34 | 75 | use html::{highlight, layout}; |
1a4d82fc | 76 | |
ea8adc8c XL |
77 | use html_diff; |
78 | ||
85aaf69f | 79 | /// A pair of name and its optional document. |
c34b1796 | 80 | pub type NameDoc = (String, Option<String>); |
85aaf69f | 81 | |
1a4d82fc JJ |
82 | /// Major driving force in all rustdoc rendering. This contains information |
83 | /// about where in the tree-like hierarchy rendering is occurring and controls | |
84 | /// how the current page is being rendered. | |
85 | /// | |
86 | /// It is intended that this context is a lightweight object which can be fairly | |
87 | /// easily cloned because it is cloned per work-job (about once per item in the | |
88 | /// rustdoc tree). | |
89 | #[derive(Clone)] | |
90 | pub struct Context { | |
91 | /// Current hierarchy of components leading down to what's currently being | |
92 | /// rendered | |
93 | pub current: Vec<String>, | |
1a4d82fc JJ |
94 | /// The current destination folder of where HTML artifacts should be placed. |
95 | /// This changes as the context descends into the module hierarchy. | |
c34b1796 | 96 | pub dst: PathBuf, |
54a0048b SL |
97 | /// A flag, which when `true`, will render pages which redirect to the |
98 | /// real location of an item. This is used to allow external links to | |
99 | /// publicly reused items to redirect to the right location. | |
100 | pub render_redirect_pages: bool, | |
101 | pub shared: Arc<SharedContext>, | |
cc61c64b | 102 | pub render_type: RenderType, |
54a0048b SL |
103 | } |
104 | ||
105 | pub struct SharedContext { | |
106 | /// The path to the crate root source minus the file name. | |
107 | /// Used for simplifying paths to the highlighted source code files. | |
108 | pub src_root: PathBuf, | |
1a4d82fc JJ |
109 | /// This describes the layout of each page, and is not modified after |
110 | /// creation of the context (contains info like the favicon and added html). | |
111 | pub layout: layout::Layout, | |
1a4d82fc JJ |
112 | /// This flag indicates whether [src] links should be generated or not. If |
113 | /// the source files are present in the html rendering, then this will be | |
114 | /// `true`. | |
115 | pub include_sources: bool, | |
54a0048b | 116 | /// The local file sources we've emitted and their respective url-paths. |
476ff2be | 117 | pub local_sources: FxHashMap<PathBuf, String>, |
1a4d82fc | 118 | /// All the passes that were run on this crate. |
476ff2be | 119 | pub passes: FxHashSet<String>, |
e9174d1e SL |
120 | /// The base-URL of the issue tracker for when an item has been tagged with |
121 | /// an issue number. | |
122 | pub issue_tracker_base_url: Option<String>, | |
54a0048b SL |
123 | /// The given user css file which allow to customize the generated |
124 | /// documentation theme. | |
125 | pub css_file_extension: Option<PathBuf>, | |
ea8adc8c XL |
126 | /// Warnings for the user if rendering would differ using different markdown |
127 | /// parsers. | |
128 | pub markdown_warnings: RefCell<Vec<(Span, String, Vec<html_diff::Difference>)>>, | |
abe05a73 XL |
129 | /// The directories that have already been created in this doc run. Used to reduce the number |
130 | /// of spurious `create_dir_all` calls. | |
131 | pub created_dirs: RefCell<FxHashSet<PathBuf>>, | |
ff7c6d11 XL |
132 | /// This flag indicates whether listings of modules (in the side bar and documentation itself) |
133 | /// should be ordered alphabetically or in order of appearance (in the source code). | |
134 | pub sort_modules_alphabetically: bool, | |
2c00a5a8 XL |
135 | /// Additional themes to be added to the generated docs. |
136 | pub themes: Vec<PathBuf>, | |
abe05a73 XL |
137 | } |
138 | ||
139 | impl SharedContext { | |
140 | fn ensure_dir(&self, dst: &Path) -> io::Result<()> { | |
141 | let mut dirs = self.created_dirs.borrow_mut(); | |
142 | if !dirs.contains(dst) { | |
143 | fs::create_dir_all(dst)?; | |
144 | dirs.insert(dst.to_path_buf()); | |
145 | } | |
146 | ||
147 | Ok(()) | |
148 | } | |
1a4d82fc JJ |
149 | } |
150 | ||
ff7c6d11 XL |
151 | impl SharedContext { |
152 | /// Returns whether the `collapse-docs` pass was run on this crate. | |
153 | pub fn was_collapsed(&self) -> bool { | |
154 | self.passes.contains("collapse-docs") | |
155 | } | |
156 | ||
157 | /// Based on whether the `collapse-docs` pass was run, return either the `doc_value` or the | |
158 | /// `collapsed_doc_value` of the given item. | |
159 | pub fn maybe_collapsed_doc_value<'a>(&self, item: &'a clean::Item) -> Option<Cow<'a, str>> { | |
160 | if self.was_collapsed() { | |
161 | item.collapsed_doc_value().map(|s| s.into()) | |
162 | } else { | |
163 | item.doc_value().map(|s| s.into()) | |
164 | } | |
165 | } | |
166 | } | |
167 | ||
1a4d82fc JJ |
168 | /// Indicates where an external crate can be found. |
169 | pub enum ExternalLocation { | |
170 | /// Remote URL root of the external crate | |
171 | Remote(String), | |
172 | /// This external crate can be found in the local doc/ folder | |
173 | Local, | |
174 | /// The external crate could not be found. | |
175 | Unknown, | |
176 | } | |
177 | ||
2c00a5a8 | 178 | /// Metadata about implementations for a type or trait. |
1a4d82fc JJ |
179 | #[derive(Clone)] |
180 | pub struct Impl { | |
a7813a04 | 181 | pub impl_item: clean::Item, |
1a4d82fc JJ |
182 | } |
183 | ||
9346a6ac | 184 | impl Impl { |
a7813a04 XL |
185 | fn inner_impl(&self) -> &clean::Impl { |
186 | match self.impl_item.inner { | |
187 | clean::ImplItem(ref impl_) => impl_, | |
188 | _ => panic!("non-impl item found in impl") | |
189 | } | |
190 | } | |
191 | ||
e9174d1e | 192 | fn trait_did(&self) -> Option<DefId> { |
a7813a04 | 193 | self.inner_impl().trait_.def_id() |
9346a6ac AL |
194 | } |
195 | } | |
196 | ||
b039eaaf SL |
197 | #[derive(Debug)] |
198 | pub struct Error { | |
199 | file: PathBuf, | |
200 | error: io::Error, | |
201 | } | |
202 | ||
203 | impl error::Error for Error { | |
204 | fn description(&self) -> &str { | |
205 | self.error.description() | |
206 | } | |
207 | } | |
208 | ||
209 | impl Display for Error { | |
210 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { | |
211 | write!(f, "\"{}\": {}", self.file.display(), self.error) | |
212 | } | |
213 | } | |
214 | ||
215 | impl Error { | |
216 | pub fn new(e: io::Error, file: &Path) -> Error { | |
217 | Error { | |
218 | file: file.to_path_buf(), | |
219 | error: e, | |
220 | } | |
221 | } | |
222 | } | |
223 | ||
2c00a5a8 XL |
224 | macro_rules! try_none { |
225 | ($e:expr, $file:expr) => ({ | |
226 | use std::io; | |
227 | match $e { | |
228 | Some(e) => e, | |
229 | None => return Err(Error::new(io::Error::new(io::ErrorKind::Other, "not found"), | |
230 | $file)) | |
231 | } | |
232 | }) | |
233 | } | |
234 | ||
b039eaaf SL |
235 | macro_rules! try_err { |
236 | ($e:expr, $file:expr) => ({ | |
237 | match $e { | |
238 | Ok(e) => e, | |
239 | Err(e) => return Err(Error::new(e, $file)), | |
240 | } | |
241 | }) | |
242 | } | |
243 | ||
1a4d82fc JJ |
244 | /// This cache is used to store information about the `clean::Crate` being |
245 | /// rendered in order to provide more useful documentation. This contains | |
246 | /// information like all implementors of a trait, all traits a type implements, | |
247 | /// documentation for all known traits, etc. | |
248 | /// | |
249 | /// This structure purposefully does not implement `Clone` because it's intended | |
250 | /// to be a fairly large and expensive structure to clone. Instead this adheres | |
251 | /// to `Send` so it may be stored in a `Arc` instance and shared among the various | |
bd371182 | 252 | /// rendering threads. |
1a4d82fc JJ |
253 | #[derive(Default)] |
254 | pub struct Cache { | |
255 | /// Mapping of typaram ids to the name of the type parameter. This is used | |
256 | /// when pretty-printing a type (so pretty printing doesn't have to | |
257 | /// painfully maintain a context like this) | |
476ff2be | 258 | pub typarams: FxHashMap<DefId, String>, |
1a4d82fc JJ |
259 | |
260 | /// Maps a type id to all known implementations for that type. This is only | |
261 | /// recognized for intra-crate `ResolvedPath` types, and is used to print | |
262 | /// out extra documentation on the page of an enum/struct. | |
263 | /// | |
264 | /// The values of the map are a list of implementations and documentation | |
265 | /// found on that implementation. | |
476ff2be | 266 | pub impls: FxHashMap<DefId, Vec<Impl>>, |
1a4d82fc JJ |
267 | |
268 | /// Maintains a mapping of local crate node ids to the fully qualified name | |
269 | /// and "short type description" of that node. This is used when generating | |
270 | /// URLs when a type is being linked to. External paths are not located in | |
271 | /// this map because the `External` type itself has all the information | |
272 | /// necessary. | |
476ff2be | 273 | pub paths: FxHashMap<DefId, (Vec<String>, ItemType)>, |
1a4d82fc JJ |
274 | |
275 | /// Similar to `paths`, but only holds external paths. This is only used for | |
276 | /// generating explicit hyperlinks to other crates. | |
476ff2be | 277 | pub external_paths: FxHashMap<DefId, (Vec<String>, ItemType)>, |
1a4d82fc JJ |
278 | |
279 | /// This map contains information about all known traits of this crate. | |
280 | /// Implementations of a crate should inherit the documentation of the | |
281 | /// parent trait if no extra documentation is specified, and default methods | |
282 | /// should show up in documentation about trait implementations. | |
476ff2be | 283 | pub traits: FxHashMap<DefId, clean::Trait>, |
1a4d82fc JJ |
284 | |
285 | /// When rendering traits, it's often useful to be able to list all | |
286 | /// implementors of the trait, and this mapping is exactly, that: a mapping | |
287 | /// of trait ids to the list of known implementors of the trait | |
2c00a5a8 | 288 | pub implementors: FxHashMap<DefId, Vec<Impl>>, |
1a4d82fc JJ |
289 | |
290 | /// Cache of where external crate documentation can be found. | |
476ff2be | 291 | pub extern_locations: FxHashMap<CrateNum, (String, PathBuf, ExternalLocation)>, |
1a4d82fc JJ |
292 | |
293 | /// Cache of where documentation for primitives can be found. | |
476ff2be | 294 | pub primitive_locations: FxHashMap<clean::PrimitiveType, DefId>, |
1a4d82fc | 295 | |
a7813a04 XL |
296 | // Note that external items for which `doc(hidden)` applies to are shown as |
297 | // non-reachable while local items aren't. This is because we're reusing | |
298 | // the access levels from crateanalysis. | |
299 | pub access_levels: Arc<AccessLevels<DefId>>, | |
300 | ||
abe05a73 XL |
301 | /// The version of the crate being documented, if given fron the `--crate-version` flag. |
302 | pub crate_version: Option<String>, | |
303 | ||
1a4d82fc JJ |
304 | // Private fields only used when initially crawling a crate to build a cache |
305 | ||
306 | stack: Vec<String>, | |
e9174d1e | 307 | parent_stack: Vec<DefId>, |
7453a54e | 308 | parent_is_trait_impl: bool, |
1a4d82fc | 309 | search_index: Vec<IndexItem>, |
54a0048b | 310 | stripped_mod: bool, |
e9174d1e | 311 | deref_trait_did: Option<DefId>, |
9e0c209e | 312 | deref_mut_trait_did: Option<DefId>, |
041b39d2 | 313 | owned_box_did: Option<DefId>, |
ea8adc8c | 314 | masked_crates: FxHashSet<CrateNum>, |
1a4d82fc JJ |
315 | |
316 | // In rare case where a structure is defined in one module but implemented | |
317 | // in another, if the implementing module is parsed before defining module, | |
318 | // then the fully qualified name of the structure isn't presented in `paths` | |
319 | // yet when its implementation methods are being indexed. Caches such methods | |
320 | // and their parent id here and indexes them at the end of crate parsing. | |
9e0c209e | 321 | orphan_impl_items: Vec<(DefId, clean::Item)>, |
1a4d82fc JJ |
322 | } |
323 | ||
a7813a04 XL |
324 | /// Temporary storage for data obtained during `RustdocVisitor::clean()`. |
325 | /// Later on moved into `CACHE_KEY`. | |
326 | #[derive(Default)] | |
327 | pub struct RenderInfo { | |
476ff2be | 328 | pub inlined: FxHashSet<DefId>, |
a7813a04 | 329 | pub external_paths: ::core::ExternalPaths, |
476ff2be | 330 | pub external_typarams: FxHashMap<DefId, String>, |
a7813a04 | 331 | pub deref_trait_did: Option<DefId>, |
9e0c209e | 332 | pub deref_mut_trait_did: Option<DefId>, |
041b39d2 | 333 | pub owned_box_did: Option<DefId>, |
a7813a04 XL |
334 | } |
335 | ||
1a4d82fc JJ |
336 | /// Helper struct to render all source code to HTML pages |
337 | struct SourceCollector<'a> { | |
54a0048b | 338 | scx: &'a mut SharedContext, |
1a4d82fc | 339 | |
1a4d82fc | 340 | /// Root destination to place all HTML output into |
c34b1796 | 341 | dst: PathBuf, |
1a4d82fc JJ |
342 | } |
343 | ||
344 | /// Wrapper struct to render the source code of a file. This will do things like | |
345 | /// adding line numbers to the left-hand side. | |
346 | struct Source<'a>(&'a str); | |
347 | ||
348 | // Helper structs for rendering items/sidebars and carrying along contextual | |
349 | // information | |
350 | ||
c34b1796 | 351 | #[derive(Copy, Clone)] |
1a4d82fc JJ |
352 | struct Item<'a> { |
353 | cx: &'a Context, | |
354 | item: &'a clean::Item, | |
355 | } | |
356 | ||
357 | struct Sidebar<'a> { cx: &'a Context, item: &'a clean::Item, } | |
358 | ||
359 | /// Struct representing one entry in the JS search index. These are all emitted | |
360 | /// by hand to a large JS file at the end of cache-creation. | |
361 | struct IndexItem { | |
362 | ty: ItemType, | |
363 | name: String, | |
364 | path: String, | |
365 | desc: String, | |
e9174d1e | 366 | parent: Option<DefId>, |
7453a54e | 367 | parent_idx: Option<usize>, |
c34b1796 AL |
368 | search_type: Option<IndexItemFunctionType>, |
369 | } | |
370 | ||
7453a54e SL |
371 | impl ToJson for IndexItem { |
372 | fn to_json(&self) -> Json { | |
373 | assert_eq!(self.parent.is_some(), self.parent_idx.is_some()); | |
374 | ||
375 | let mut data = Vec::with_capacity(6); | |
376 | data.push((self.ty as usize).to_json()); | |
377 | data.push(self.name.to_json()); | |
378 | data.push(self.path.to_json()); | |
379 | data.push(self.desc.to_json()); | |
380 | data.push(self.parent_idx.to_json()); | |
381 | data.push(self.search_type.to_json()); | |
382 | ||
383 | Json::Array(data) | |
384 | } | |
385 | } | |
386 | ||
c34b1796 AL |
387 | /// A type used for the search index. |
388 | struct Type { | |
389 | name: Option<String>, | |
abe05a73 | 390 | generics: Option<Vec<String>>, |
c34b1796 AL |
391 | } |
392 | ||
7453a54e SL |
393 | impl ToJson for Type { |
394 | fn to_json(&self) -> Json { | |
c34b1796 | 395 | match self.name { |
7453a54e SL |
396 | Some(ref name) => { |
397 | let mut data = BTreeMap::new(); | |
398 | data.insert("name".to_owned(), name.to_json()); | |
abe05a73 XL |
399 | if let Some(ref generics) = self.generics { |
400 | data.insert("generics".to_owned(), generics.to_json()); | |
401 | } | |
7453a54e SL |
402 | Json::Object(data) |
403 | }, | |
404 | None => Json::Null | |
c34b1796 AL |
405 | } |
406 | } | |
407 | } | |
408 | ||
409 | /// Full type of functions/methods in the search index. | |
410 | struct IndexItemFunctionType { | |
411 | inputs: Vec<Type>, | |
412 | output: Option<Type> | |
413 | } | |
414 | ||
7453a54e SL |
415 | impl ToJson for IndexItemFunctionType { |
416 | fn to_json(&self) -> Json { | |
c34b1796 | 417 | // If we couldn't figure out a type, just write `null`. |
7453a54e SL |
418 | if self.inputs.iter().chain(self.output.iter()).any(|ref i| i.name.is_none()) { |
419 | Json::Null | |
420 | } else { | |
421 | let mut data = BTreeMap::new(); | |
422 | data.insert("inputs".to_owned(), self.inputs.to_json()); | |
423 | data.insert("output".to_owned(), self.output.to_json()); | |
424 | Json::Object(data) | |
c34b1796 | 425 | } |
c34b1796 | 426 | } |
1a4d82fc JJ |
427 | } |
428 | ||
429 | // TLS keys used to carry information around during rendering. | |
430 | ||
431 | thread_local!(static CACHE_KEY: RefCell<Arc<Cache>> = Default::default()); | |
432 | thread_local!(pub static CURRENT_LOCATION_KEY: RefCell<Vec<String>> = | |
433 | RefCell::new(Vec::new())); | |
ff7c6d11 | 434 | thread_local!(pub static USED_ID_MAP: RefCell<FxHashMap<String, usize>> = |
92a42be0 SL |
435 | RefCell::new(init_ids())); |
436 | ||
2c00a5a8 | 437 | pub fn render_text<T, F: FnMut(RenderType) -> T>(mut render: F) -> (T, T) { |
ff7c6d11 XL |
438 | // Save the state of USED_ID_MAP so it only gets updated once even |
439 | // though we're rendering twice. | |
440 | let orig_used_id_map = USED_ID_MAP.with(|map| map.borrow().clone()); | |
441 | let hoedown_output = render(RenderType::Hoedown); | |
442 | USED_ID_MAP.with(|map| *map.borrow_mut() = orig_used_id_map); | |
443 | let pulldown_output = render(RenderType::Pulldown); | |
444 | (hoedown_output, pulldown_output) | |
445 | } | |
446 | ||
476ff2be | 447 | fn init_ids() -> FxHashMap<String, usize> { |
92a42be0 SL |
448 | [ |
449 | "main", | |
450 | "search", | |
451 | "help", | |
452 | "TOC", | |
453 | "render-detail", | |
454 | "associated-types", | |
455 | "associated-const", | |
456 | "required-methods", | |
457 | "provided-methods", | |
458 | "implementors", | |
459 | "implementors-list", | |
460 | "methods", | |
461 | "deref-methods", | |
462 | "implementations", | |
abe05a73 | 463 | ].into_iter().map(|id| (String::from(*id), 1)).collect() |
92a42be0 SL |
464 | } |
465 | ||
466 | /// This method resets the local table of used ID attributes. This is typically | |
467 | /// used at the beginning of rendering an entire HTML page to reset from the | |
468 | /// previous state (if any). | |
54a0048b SL |
469 | pub fn reset_ids(embedded: bool) { |
470 | USED_ID_MAP.with(|s| { | |
471 | *s.borrow_mut() = if embedded { | |
472 | init_ids() | |
473 | } else { | |
476ff2be | 474 | FxHashMap() |
54a0048b SL |
475 | }; |
476 | }); | |
92a42be0 SL |
477 | } |
478 | ||
479 | pub fn derive_id(candidate: String) -> String { | |
480 | USED_ID_MAP.with(|map| { | |
481 | let id = match map.borrow_mut().get_mut(&candidate) { | |
482 | None => candidate, | |
483 | Some(a) => { | |
484 | let id = format!("{}-{}", candidate, *a); | |
485 | *a += 1; | |
486 | id | |
487 | } | |
488 | }; | |
489 | ||
490 | map.borrow_mut().insert(id.clone(), 1); | |
491 | id | |
492 | }) | |
493 | } | |
1a4d82fc JJ |
494 | |
495 | /// Generates the documentation for `crate` into the directory `dst` | |
496 | pub fn run(mut krate: clean::Crate, | |
497 | external_html: &ExternalHtml, | |
476ff2be | 498 | playground_url: Option<String>, |
c34b1796 | 499 | dst: PathBuf, |
476ff2be | 500 | passes: FxHashSet<String>, |
a7813a04 | 501 | css_file_extension: Option<PathBuf>, |
cc61c64b | 502 | renderinfo: RenderInfo, |
ff7c6d11 XL |
503 | render_type: RenderType, |
504 | sort_modules_alphabetically: bool, | |
2c00a5a8 XL |
505 | deny_render_differences: bool, |
506 | themes: Vec<PathBuf>) -> Result<(), Error> { | |
ff7c6d11 XL |
507 | let src_root = match krate.src { |
508 | FileName::Real(ref p) => match p.parent() { | |
509 | Some(p) => p.to_path_buf(), | |
510 | None => PathBuf::new(), | |
511 | }, | |
512 | _ => PathBuf::new(), | |
c34b1796 | 513 | }; |
54a0048b | 514 | let mut scx = SharedContext { |
3b2f2976 XL |
515 | src_root, |
516 | passes, | |
54a0048b | 517 | include_sources: true, |
476ff2be | 518 | local_sources: FxHashMap(), |
54a0048b | 519 | issue_tracker_base_url: None, |
1a4d82fc JJ |
520 | layout: layout::Layout { |
521 | logo: "".to_string(), | |
522 | favicon: "".to_string(), | |
523 | external_html: external_html.clone(), | |
524 | krate: krate.name.clone(), | |
1a4d82fc | 525 | }, |
54a0048b | 526 | css_file_extension: css_file_extension.clone(), |
ea8adc8c | 527 | markdown_warnings: RefCell::new(vec![]), |
abe05a73 | 528 | created_dirs: RefCell::new(FxHashSet()), |
ff7c6d11 | 529 | sort_modules_alphabetically, |
2c00a5a8 | 530 | themes, |
1a4d82fc JJ |
531 | }; |
532 | ||
476ff2be SL |
533 | // If user passed in `--playground-url` arg, we fill in crate name here |
534 | if let Some(url) = playground_url { | |
535 | markdown::PLAYGROUND.with(|slot| { | |
536 | *slot.borrow_mut() = Some((Some(krate.name.clone()), url)); | |
537 | }); | |
538 | } | |
539 | ||
1a4d82fc JJ |
540 | // Crawl the crate attributes looking for attributes which control how we're |
541 | // going to emit HTML | |
476ff2be SL |
542 | if let Some(attrs) = krate.module.as_ref().map(|m| &m.attrs) { |
543 | for attr in attrs.lists("doc") { | |
544 | let name = attr.name().map(|s| s.as_str()); | |
545 | match (name.as_ref().map(|s| &s[..]), attr.value_str()) { | |
546 | (Some("html_favicon_url"), Some(s)) => { | |
54a0048b SL |
547 | scx.layout.favicon = s.to_string(); |
548 | } | |
476ff2be | 549 | (Some("html_logo_url"), Some(s)) => { |
54a0048b SL |
550 | scx.layout.logo = s.to_string(); |
551 | } | |
476ff2be | 552 | (Some("html_playground_url"), Some(s)) => { |
c30ab7b3 | 553 | markdown::PLAYGROUND.with(|slot| { |
476ff2be SL |
554 | let name = krate.name.clone(); |
555 | *slot.borrow_mut() = Some((Some(name), s.to_string())); | |
54a0048b | 556 | }); |
1a4d82fc | 557 | } |
476ff2be | 558 | (Some("issue_tracker_base_url"), Some(s)) => { |
54a0048b SL |
559 | scx.issue_tracker_base_url = Some(s.to_string()); |
560 | } | |
476ff2be | 561 | (Some("html_no_source"), None) if attr.is_word() => { |
54a0048b SL |
562 | scx.include_sources = false; |
563 | } | |
564 | _ => {} | |
1a4d82fc JJ |
565 | } |
566 | } | |
1a4d82fc | 567 | } |
041b39d2 | 568 | try_err!(fs::create_dir_all(&dst), &dst); |
54a0048b SL |
569 | krate = render_sources(&dst, &mut scx, krate)?; |
570 | let cx = Context { | |
571 | current: Vec::new(), | |
3b2f2976 | 572 | dst, |
54a0048b SL |
573 | render_redirect_pages: false, |
574 | shared: Arc::new(scx), | |
3b2f2976 | 575 | render_type, |
54a0048b | 576 | }; |
1a4d82fc JJ |
577 | |
578 | // Crawl the crate to build various caches used for the output | |
a7813a04 | 579 | let RenderInfo { |
5bcae85e | 580 | inlined: _, |
a7813a04 XL |
581 | external_paths, |
582 | external_typarams, | |
583 | deref_trait_did, | |
9e0c209e | 584 | deref_mut_trait_did, |
041b39d2 | 585 | owned_box_did, |
a7813a04 XL |
586 | } = renderinfo; |
587 | ||
5bcae85e | 588 | let external_paths = external_paths.into_iter() |
9e0c209e | 589 | .map(|(k, (v, t))| (k, (v, ItemType::from(t)))) |
5bcae85e | 590 | .collect(); |
a7813a04 | 591 | |
1a4d82fc | 592 | let mut cache = Cache { |
476ff2be | 593 | impls: FxHashMap(), |
3b2f2976 | 594 | external_paths, |
476ff2be SL |
595 | paths: FxHashMap(), |
596 | implementors: FxHashMap(), | |
1a4d82fc JJ |
597 | stack: Vec::new(), |
598 | parent_stack: Vec::new(), | |
599 | search_index: Vec::new(), | |
7453a54e | 600 | parent_is_trait_impl: false, |
476ff2be SL |
601 | extern_locations: FxHashMap(), |
602 | primitive_locations: FxHashMap(), | |
54a0048b | 603 | stripped_mod: false, |
a7813a04 | 604 | access_levels: krate.access_levels.clone(), |
abe05a73 | 605 | crate_version: krate.version.take(), |
9e0c209e | 606 | orphan_impl_items: Vec::new(), |
476ff2be | 607 | traits: mem::replace(&mut krate.external_traits, FxHashMap()), |
3b2f2976 XL |
608 | deref_trait_did, |
609 | deref_mut_trait_did, | |
610 | owned_box_did, | |
ea8adc8c | 611 | masked_crates: mem::replace(&mut krate.masked_crates, FxHashSet()), |
a7813a04 | 612 | typarams: external_typarams, |
1a4d82fc | 613 | }; |
1a4d82fc JJ |
614 | |
615 | // Cache where all our extern crates are located | |
85aaf69f | 616 | for &(n, ref e) in &krate.externs { |
ff7c6d11 XL |
617 | let src_root = match e.src { |
618 | FileName::Real(ref p) => match p.parent() { | |
619 | Some(p) => p.to_path_buf(), | |
620 | None => PathBuf::new(), | |
621 | }, | |
622 | _ => PathBuf::new(), | |
476ff2be SL |
623 | }; |
624 | cache.extern_locations.insert(n, (e.name.clone(), src_root, | |
d9579d0f | 625 | extern_location(e, &cx.dst))); |
476ff2be | 626 | |
b039eaaf | 627 | let did = DefId { krate: n, index: CRATE_DEF_INDEX }; |
5bcae85e | 628 | cache.external_paths.insert(did, (vec![e.name.to_string()], ItemType::Module)); |
1a4d82fc JJ |
629 | } |
630 | ||
631 | // Cache where all known primitives have their documentation located. | |
632 | // | |
633 | // Favor linking to as local extern as possible, so iterate all crates in | |
634 | // reverse topological order. | |
476ff2be SL |
635 | for &(_, ref e) in krate.externs.iter().rev() { |
636 | for &(def_id, prim, _) in &e.primitives { | |
637 | cache.primitive_locations.insert(prim, def_id); | |
1a4d82fc JJ |
638 | } |
639 | } | |
476ff2be SL |
640 | for &(def_id, prim, _) in &krate.primitives { |
641 | cache.primitive_locations.insert(prim, def_id); | |
1a4d82fc JJ |
642 | } |
643 | ||
d9579d0f AL |
644 | cache.stack.push(krate.name.clone()); |
645 | krate = cache.fold_crate(krate); | |
646 | ||
1a4d82fc | 647 | // Build our search index |
b039eaaf | 648 | let index = build_index(&krate, &mut cache); |
1a4d82fc JJ |
649 | |
650 | // Freeze the cache now that the index has been built. Put an Arc into TLS | |
651 | // for future parallelization opportunities | |
652 | let cache = Arc::new(cache); | |
653 | CACHE_KEY.with(|v| *v.borrow_mut() = cache.clone()); | |
654 | CURRENT_LOCATION_KEY.with(|s| s.borrow_mut().clear()); | |
655 | ||
54a0048b | 656 | write_shared(&cx, &krate, &*cache, index)?; |
1a4d82fc | 657 | |
ea8adc8c XL |
658 | let scx = cx.shared.clone(); |
659 | ||
1a4d82fc | 660 | // And finally render the whole crate's documentation |
ea8adc8c XL |
661 | let result = cx.krate(krate); |
662 | ||
663 | let markdown_warnings = scx.markdown_warnings.borrow(); | |
664 | if !markdown_warnings.is_empty() { | |
665 | let mut intro_msg = false; | |
666 | for &(ref span, ref text, ref diffs) in &*markdown_warnings { | |
667 | for d in diffs { | |
668 | render_difference(d, &mut intro_msg, span, text); | |
669 | } | |
670 | } | |
ff7c6d11 XL |
671 | |
672 | if deny_render_differences { | |
673 | println!("Aborting with {} rendering differences", markdown_warnings.len()); | |
674 | ::std::process::exit(1); | |
675 | } | |
ea8adc8c XL |
676 | } |
677 | ||
678 | result | |
679 | } | |
680 | ||
681 | // A short, single-line view of `s`. | |
682 | fn concise_str(mut s: &str) -> String { | |
683 | if s.contains('\n') { | |
684 | s = s.lines().next().expect("Impossible! We just found a newline"); | |
685 | } | |
686 | if s.len() > 70 { | |
687 | let mut lo = 50; | |
688 | let mut hi = s.len() - 20; | |
689 | while !s.is_char_boundary(lo) { | |
690 | lo -= 1; | |
691 | } | |
692 | while !s.is_char_boundary(hi) { | |
693 | hi += 1; | |
694 | } | |
695 | return format!("{} ... {}", &s[..lo], &s[hi..]); | |
696 | } | |
697 | s.to_owned() | |
698 | } | |
699 | ||
700 | // Returns short versions of s1 and s2, starting from where the strings differ. | |
701 | fn concise_compared_strs(s1: &str, s2: &str) -> (String, String) { | |
702 | let s1 = s1.trim(); | |
703 | let s2 = s2.trim(); | |
704 | if !s1.contains('\n') && !s2.contains('\n') && s1.len() <= 70 && s2.len() <= 70 { | |
705 | return (s1.to_owned(), s2.to_owned()); | |
706 | } | |
707 | ||
708 | let mut start_byte = 0; | |
709 | for (c1, c2) in s1.chars().zip(s2.chars()) { | |
710 | if c1 != c2 { | |
711 | break; | |
712 | } | |
713 | ||
714 | start_byte += c1.len_utf8(); | |
715 | } | |
716 | ||
717 | if start_byte == 0 { | |
718 | return (concise_str(s1), concise_str(s2)); | |
719 | } | |
720 | ||
721 | let s1 = &s1[start_byte..]; | |
722 | let s2 = &s2[start_byte..]; | |
723 | (format!("...{}", concise_str(s1)), format!("...{}", concise_str(s2))) | |
724 | } | |
725 | ||
ea8adc8c XL |
726 | fn print_message(msg: &str, intro_msg: &mut bool, span: &Span, text: &str) { |
727 | if !*intro_msg { | |
728 | println!("WARNING: documentation for this crate may be rendered \ | |
729 | differently using the new Pulldown renderer."); | |
730 | println!(" See https://github.com/rust-lang/rust/issues/44229 for details."); | |
731 | *intro_msg = true; | |
732 | } | |
733 | println!("WARNING: rendering difference in `{}`", concise_str(text)); | |
734 | println!(" --> {}:{}:{}", span.filename, span.loline, span.locol); | |
735 | println!("{}", msg); | |
736 | } | |
737 | ||
ff7c6d11 XL |
738 | pub fn render_difference(diff: &html_diff::Difference, |
739 | intro_msg: &mut bool, | |
740 | span: &Span, | |
741 | text: &str) { | |
ea8adc8c XL |
742 | match *diff { |
743 | html_diff::Difference::NodeType { ref elem, ref opposite_elem } => { | |
744 | print_message(&format!(" {} Types differ: expected: `{}`, found: `{}`", | |
745 | elem.path, elem.element_name, opposite_elem.element_name), | |
746 | intro_msg, span, text); | |
747 | } | |
748 | html_diff::Difference::NodeName { ref elem, ref opposite_elem } => { | |
749 | print_message(&format!(" {} Tags differ: expected: `{}`, found: `{}`", | |
750 | elem.path, elem.element_name, opposite_elem.element_name), | |
751 | intro_msg, span, text); | |
752 | } | |
753 | html_diff::Difference::NodeAttributes { ref elem, | |
754 | ref elem_attributes, | |
755 | ref opposite_elem_attributes, | |
756 | .. } => { | |
757 | print_message(&format!(" {} Attributes differ in `{}`: expected: `{:?}`, \ | |
758 | found: `{:?}`", | |
759 | elem.path, elem.element_name, elem_attributes, | |
760 | opposite_elem_attributes), | |
761 | intro_msg, span, text); | |
762 | } | |
763 | html_diff::Difference::NodeText { ref elem, ref elem_text, ref opposite_elem_text, .. } => { | |
764 | if elem_text.split("\n") | |
765 | .zip(opposite_elem_text.split("\n")) | |
766 | .any(|(a, b)| a.trim() != b.trim()) { | |
767 | let (s1, s2) = concise_compared_strs(elem_text, opposite_elem_text); | |
768 | print_message(&format!(" {} Text differs:\n expected: `{}`\n \ | |
769 | found: `{}`", | |
770 | elem.path, s1, s2), | |
771 | intro_msg, span, text); | |
772 | } | |
773 | } | |
774 | html_diff::Difference::NotPresent { ref elem, ref opposite_elem } => { | |
775 | if let Some(ref elem) = *elem { | |
776 | print_message(&format!(" {} One element is missing: expected: `{}`", | |
777 | elem.path, elem.element_name), | |
778 | intro_msg, span, text); | |
779 | } else if let Some(ref elem) = *opposite_elem { | |
780 | if elem.element_name.is_empty() { | |
781 | print_message(&format!(" {} One element is missing: expected: `{}`", | |
782 | elem.path, concise_str(&elem.element_content)), | |
783 | intro_msg, span, text); | |
784 | } else { | |
785 | print_message(&format!(" {} Unexpected element `{}`: found: `{}`", | |
786 | elem.path, elem.element_name, | |
787 | concise_str(&elem.element_content)), | |
788 | intro_msg, span, text); | |
789 | } | |
790 | } | |
791 | } | |
792 | } | |
1a4d82fc JJ |
793 | } |
794 | ||
7453a54e | 795 | /// Build the search index from the collected metadata |
b039eaaf | 796 | fn build_index(krate: &clean::Crate, cache: &mut Cache) -> String { |
476ff2be | 797 | let mut nodeid_to_pathid = FxHashMap(); |
7453a54e SL |
798 | let mut crate_items = Vec::with_capacity(cache.search_index.len()); |
799 | let mut crate_paths = Vec::<Json>::new(); | |
800 | ||
801 | let Cache { ref mut search_index, | |
9e0c209e | 802 | ref orphan_impl_items, |
7453a54e SL |
803 | ref mut paths, .. } = *cache; |
804 | ||
9e0c209e | 805 | // Attach all orphan items to the type's definition if the type |
7453a54e | 806 | // has since been learned. |
9e0c209e | 807 | for &(did, ref item) in orphan_impl_items { |
3157f602 XL |
808 | if let Some(&(ref fqp, _)) = paths.get(&did) { |
809 | search_index.push(IndexItem { | |
c30ab7b3 | 810 | ty: item.type_(), |
3157f602 XL |
811 | name: item.name.clone().unwrap(), |
812 | path: fqp[..fqp.len() - 1].join("::"), | |
32a655c1 | 813 | desc: plain_summary_line(item.doc_value()), |
3157f602 XL |
814 | parent: Some(did), |
815 | parent_idx: None, | |
816 | search_type: get_index_search_type(&item), | |
817 | }); | |
7453a54e SL |
818 | } |
819 | } | |
820 | ||
821 | // Reduce `NodeId` in paths into smaller sequential numbers, | |
822 | // and prune the paths that do not appear in the index. | |
823 | let mut lastpath = String::new(); | |
824 | let mut lastpathid = 0usize; | |
825 | ||
826 | for item in search_index { | |
827 | item.parent_idx = item.parent.map(|nodeid| { | |
828 | if nodeid_to_pathid.contains_key(&nodeid) { | |
829 | *nodeid_to_pathid.get(&nodeid).unwrap() | |
830 | } else { | |
831 | let pathid = lastpathid; | |
832 | nodeid_to_pathid.insert(nodeid, pathid); | |
833 | lastpathid += 1; | |
1a4d82fc | 834 | |
7453a54e SL |
835 | let &(ref fqp, short) = paths.get(&nodeid).unwrap(); |
836 | crate_paths.push(((short as usize), fqp.last().unwrap().clone()).to_json()); | |
837 | pathid | |
1a4d82fc | 838 | } |
7453a54e | 839 | }); |
1a4d82fc | 840 | |
7453a54e | 841 | // Omit the parent path if it is same to that of the prior item. |
1a4d82fc | 842 | if lastpath == item.path { |
7453a54e | 843 | item.path.clear(); |
1a4d82fc | 844 | } else { |
7453a54e | 845 | lastpath = item.path.clone(); |
1a4d82fc | 846 | } |
7453a54e | 847 | crate_items.push(item.to_json()); |
1a4d82fc JJ |
848 | } |
849 | ||
7453a54e | 850 | let crate_doc = krate.module.as_ref().map(|module| { |
32a655c1 | 851 | plain_summary_line(module.doc_value()) |
7453a54e | 852 | }).unwrap_or(String::new()); |
1a4d82fc | 853 | |
7453a54e SL |
854 | let mut crate_data = BTreeMap::new(); |
855 | crate_data.insert("doc".to_owned(), Json::String(crate_doc)); | |
856 | crate_data.insert("items".to_owned(), Json::Array(crate_items)); | |
857 | crate_data.insert("paths".to_owned(), Json::Array(crate_paths)); | |
1a4d82fc | 858 | |
7453a54e SL |
859 | // Collect the index into a string |
860 | format!("searchIndex[{}] = {};", | |
861 | as_json(&krate.name), | |
862 | Json::Object(crate_data)) | |
1a4d82fc JJ |
863 | } |
864 | ||
865 | fn write_shared(cx: &Context, | |
866 | krate: &clean::Crate, | |
867 | cache: &Cache, | |
b039eaaf | 868 | search_index: String) -> Result<(), Error> { |
1a4d82fc JJ |
869 | // Write out the shared files. Note that these are shared among all rustdoc |
870 | // docs placed in the output directory, so this needs to be a synchronized | |
871 | // operation with respect to all other rustdocs running around. | |
9e0c209e | 872 | let _lock = flock::Lock::panicking_new(&cx.dst.join(".lock"), true, true, true); |
1a4d82fc JJ |
873 | |
874 | // Add all the static files. These may already exist, but we just | |
875 | // overwrite them anyway to make sure that they're fresh and up-to-date. | |
54a0048b | 876 | |
54a0048b SL |
877 | write(cx.dst.join("rustdoc.css"), |
878 | include_bytes!("static/rustdoc.css"))?; | |
2c00a5a8 XL |
879 | |
880 | // To avoid "main.css" to be overwritten, we'll first run over the received themes and only | |
881 | // then we'll run over the "official" styles. | |
882 | let mut themes: HashSet<String> = HashSet::new(); | |
883 | ||
884 | for entry in &cx.shared.themes { | |
885 | let mut content = Vec::with_capacity(100000); | |
886 | ||
887 | let mut f = try_err!(File::open(&entry), &entry); | |
888 | try_err!(f.read_to_end(&mut content), &entry); | |
889 | write(cx.dst.join(try_none!(entry.file_name(), &entry)), content.as_slice())?; | |
890 | themes.insert(try_none!(try_none!(entry.file_stem(), &entry).to_str(), &entry).to_owned()); | |
891 | } | |
892 | ||
893 | write(cx.dst.join("brush.svg"), | |
894 | include_bytes!("static/brush.svg"))?; | |
54a0048b | 895 | write(cx.dst.join("main.css"), |
2c00a5a8 XL |
896 | include_bytes!("static/themes/main.css"))?; |
897 | themes.insert("main".to_owned()); | |
898 | write(cx.dst.join("dark.css"), | |
899 | include_bytes!("static/themes/dark.css"))?; | |
900 | themes.insert("dark".to_owned()); | |
901 | ||
902 | let mut themes: Vec<&String> = themes.iter().collect(); | |
903 | themes.sort(); | |
904 | // To avoid theme switch latencies as much as possible, we put everything theme related | |
905 | // at the beginning of the html files into another js file. | |
906 | write(cx.dst.join("theme.js"), format!( | |
907 | r#"var themes = document.getElementById("theme-choices"); | |
908 | var themePicker = document.getElementById("theme-picker"); | |
909 | themePicker.onclick = function() {{ | |
910 | if (themes.style.display === "block") {{ | |
911 | themes.style.display = "none"; | |
912 | themePicker.style.borderBottomRightRadius = "3px"; | |
913 | themePicker.style.borderBottomLeftRadius = "3px"; | |
914 | }} else {{ | |
915 | themes.style.display = "block"; | |
916 | themePicker.style.borderBottomRightRadius = "0"; | |
917 | themePicker.style.borderBottomLeftRadius = "0"; | |
918 | }} | |
919 | }}; | |
920 | [{}].forEach(function(item) {{ | |
921 | var but = document.createElement('button'); | |
922 | but.innerHTML = item; | |
923 | but.onclick = function(el) {{ | |
924 | switchTheme(currentTheme, mainTheme, item); | |
925 | }}; | |
926 | themes.appendChild(but); | |
927 | }}); | |
928 | "#, themes.iter() | |
929 | .map(|s| format!("\"{}\"", s)) | |
930 | .collect::<Vec<String>>() | |
931 | .join(",")).as_bytes())?; | |
932 | ||
933 | write(cx.dst.join("main.js"), include_bytes!("static/main.js"))?; | |
934 | write(cx.dst.join("storage.js"), include_bytes!("static/storage.js"))?; | |
935 | ||
54a0048b | 936 | if let Some(ref css) = cx.shared.css_file_extension { |
2c00a5a8 XL |
937 | let out = cx.dst.join("theme.css"); |
938 | try_err!(fs::copy(css, out), css); | |
54a0048b SL |
939 | } |
940 | write(cx.dst.join("normalize.css"), | |
941 | include_bytes!("static/normalize.css"))?; | |
942 | write(cx.dst.join("FiraSans-Regular.woff"), | |
943 | include_bytes!("static/FiraSans-Regular.woff"))?; | |
944 | write(cx.dst.join("FiraSans-Medium.woff"), | |
945 | include_bytes!("static/FiraSans-Medium.woff"))?; | |
946 | write(cx.dst.join("FiraSans-LICENSE.txt"), | |
947 | include_bytes!("static/FiraSans-LICENSE.txt"))?; | |
948 | write(cx.dst.join("Heuristica-Italic.woff"), | |
949 | include_bytes!("static/Heuristica-Italic.woff"))?; | |
950 | write(cx.dst.join("Heuristica-LICENSE.txt"), | |
951 | include_bytes!("static/Heuristica-LICENSE.txt"))?; | |
952 | write(cx.dst.join("SourceSerifPro-Regular.woff"), | |
953 | include_bytes!("static/SourceSerifPro-Regular.woff"))?; | |
954 | write(cx.dst.join("SourceSerifPro-Bold.woff"), | |
955 | include_bytes!("static/SourceSerifPro-Bold.woff"))?; | |
956 | write(cx.dst.join("SourceSerifPro-LICENSE.txt"), | |
957 | include_bytes!("static/SourceSerifPro-LICENSE.txt"))?; | |
958 | write(cx.dst.join("SourceCodePro-Regular.woff"), | |
959 | include_bytes!("static/SourceCodePro-Regular.woff"))?; | |
960 | write(cx.dst.join("SourceCodePro-Semibold.woff"), | |
961 | include_bytes!("static/SourceCodePro-Semibold.woff"))?; | |
962 | write(cx.dst.join("SourceCodePro-LICENSE.txt"), | |
963 | include_bytes!("static/SourceCodePro-LICENSE.txt"))?; | |
964 | write(cx.dst.join("LICENSE-MIT.txt"), | |
965 | include_bytes!("static/LICENSE-MIT.txt"))?; | |
966 | write(cx.dst.join("LICENSE-APACHE.txt"), | |
967 | include_bytes!("static/LICENSE-APACHE.txt"))?; | |
968 | write(cx.dst.join("COPYRIGHT.txt"), | |
969 | include_bytes!("static/COPYRIGHT.txt"))?; | |
1a4d82fc JJ |
970 | |
971 | fn collect(path: &Path, krate: &str, | |
c34b1796 | 972 | key: &str) -> io::Result<Vec<String>> { |
1a4d82fc JJ |
973 | let mut ret = Vec::new(); |
974 | if path.exists() { | |
54a0048b SL |
975 | for line in BufReader::new(File::open(path)?).lines() { |
976 | let line = line?; | |
1a4d82fc | 977 | if !line.starts_with(key) { |
9e0c209e | 978 | continue; |
1a4d82fc | 979 | } |
7453a54e | 980 | if line.starts_with(&format!(r#"{}["{}"]"#, key, krate)) { |
9e0c209e | 981 | continue; |
1a4d82fc JJ |
982 | } |
983 | ret.push(line.to_string()); | |
984 | } | |
985 | } | |
c30ab7b3 | 986 | Ok(ret) |
1a4d82fc JJ |
987 | } |
988 | ||
989 | // Update the search index | |
990 | let dst = cx.dst.join("search-index.js"); | |
476ff2be SL |
991 | let mut all_indexes = try_err!(collect(&dst, &krate.name, "searchIndex"), &dst); |
992 | all_indexes.push(search_index); | |
993 | // Sort the indexes by crate so the file will be generated identically even | |
994 | // with rustdoc running in parallel. | |
995 | all_indexes.sort(); | |
b039eaaf SL |
996 | let mut w = try_err!(File::create(&dst), &dst); |
997 | try_err!(writeln!(&mut w, "var searchIndex = {{}};"), &dst); | |
85aaf69f | 998 | for index in &all_indexes { |
b039eaaf | 999 | try_err!(writeln!(&mut w, "{}", *index), &dst); |
1a4d82fc | 1000 | } |
b039eaaf | 1001 | try_err!(writeln!(&mut w, "initSearch(searchIndex);"), &dst); |
1a4d82fc JJ |
1002 | |
1003 | // Update the list of all implementors for traits | |
1004 | let dst = cx.dst.join("implementors"); | |
85aaf69f | 1005 | for (&did, imps) in &cache.implementors { |
1a4d82fc JJ |
1006 | // Private modules can leak through to this phase of rustdoc, which |
1007 | // could contain implementations for otherwise private types. In some | |
1008 | // rare cases we could find an implementation for an item which wasn't | |
1009 | // indexed, so we just skip this step in that case. | |
1010 | // | |
1011 | // FIXME: this is a vague explanation for why this can't be a `get`, in | |
1012 | // theory it should be... | |
1013 | let &(ref remote_path, remote_item_type) = match cache.paths.get(&did) { | |
1014 | Some(p) => p, | |
5bcae85e SL |
1015 | None => match cache.external_paths.get(&did) { |
1016 | Some(p) => p, | |
1017 | None => continue, | |
1018 | } | |
1a4d82fc JJ |
1019 | }; |
1020 | ||
3b2f2976 | 1021 | let mut have_impls = false; |
476ff2be SL |
1022 | let mut implementors = format!(r#"implementors["{}"] = ["#, krate.name); |
1023 | for imp in imps { | |
1024 | // If the trait and implementation are in the same crate, then | |
1025 | // there's no need to emit information about it (there's inlining | |
1026 | // going on). If they're in different crates then the crate defining | |
1027 | // the trait will be interested in our implementation. | |
2c00a5a8 | 1028 | if imp.impl_item.def_id.krate == did.krate { continue } |
3b2f2976 XL |
1029 | // If the implementation is from another crate then that crate |
1030 | // should add it. | |
2c00a5a8 | 1031 | if !imp.impl_item.def_id.is_local() { continue } |
3b2f2976 | 1032 | have_impls = true; |
2c00a5a8 | 1033 | write!(implementors, "{},", as_json(&imp.inner_impl().to_string())).unwrap(); |
476ff2be SL |
1034 | } |
1035 | implementors.push_str("];"); | |
1036 | ||
3b2f2976 XL |
1037 | // Only create a js file if we have impls to add to it. If the trait is |
1038 | // documented locally though we always create the file to avoid dead | |
1039 | // links. | |
1040 | if !have_impls && !cache.paths.contains_key(&did) { | |
1041 | continue; | |
1042 | } | |
1043 | ||
1a4d82fc | 1044 | let mut mydst = dst.clone(); |
85aaf69f SL |
1045 | for part in &remote_path[..remote_path.len() - 1] { |
1046 | mydst.push(part); | |
1a4d82fc | 1047 | } |
476ff2be | 1048 | try_err!(fs::create_dir_all(&mydst), &mydst); |
c34b1796 | 1049 | mydst.push(&format!("{}.{}.js", |
9e0c209e | 1050 | remote_item_type.css_class(), |
c34b1796 | 1051 | remote_path[remote_path.len() - 1])); |
1a4d82fc | 1052 | |
476ff2be SL |
1053 | let mut all_implementors = try_err!(collect(&mydst, &krate.name, "implementors"), &mydst); |
1054 | all_implementors.push(implementors); | |
1055 | // Sort the implementors by crate so the file will be generated | |
1056 | // identically even with rustdoc running in parallel. | |
1057 | all_implementors.sort(); | |
1a4d82fc | 1058 | |
476ff2be SL |
1059 | let mut f = try_err!(File::create(&mydst), &mydst); |
1060 | try_err!(writeln!(&mut f, "(function() {{var implementors = {{}};"), &mydst); | |
85aaf69f | 1061 | for implementor in &all_implementors { |
476ff2be | 1062 | try_err!(writeln!(&mut f, "{}", *implementor), &mydst); |
1a4d82fc | 1063 | } |
b039eaaf | 1064 | try_err!(writeln!(&mut f, "{}", r" |
1a4d82fc JJ |
1065 | if (window.register_implementors) { |
1066 | window.register_implementors(implementors); | |
1067 | } else { | |
1068 | window.pending_implementors = implementors; | |
1069 | } | |
b039eaaf SL |
1070 | "), &mydst); |
1071 | try_err!(writeln!(&mut f, r"}})()"), &mydst); | |
1a4d82fc JJ |
1072 | } |
1073 | Ok(()) | |
1074 | } | |
1075 | ||
54a0048b | 1076 | fn render_sources(dst: &Path, scx: &mut SharedContext, |
b039eaaf | 1077 | krate: clean::Crate) -> Result<clean::Crate, Error> { |
1a4d82fc | 1078 | info!("emitting source files"); |
041b39d2 XL |
1079 | let dst = dst.join("src").join(&krate.name); |
1080 | try_err!(fs::create_dir_all(&dst), &dst); | |
1a4d82fc | 1081 | let mut folder = SourceCollector { |
3b2f2976 XL |
1082 | dst, |
1083 | scx, | |
1a4d82fc | 1084 | }; |
1a4d82fc JJ |
1085 | Ok(folder.fold_crate(krate)) |
1086 | } | |
1087 | ||
1088 | /// Writes the entire contents of a string to a destination, not attempting to | |
1089 | /// catch any errors. | |
b039eaaf | 1090 | fn write(dst: PathBuf, contents: &[u8]) -> Result<(), Error> { |
2c00a5a8 | 1091 | Ok(try_err!(fs::write(&dst, contents), &dst)) |
1a4d82fc JJ |
1092 | } |
1093 | ||
1a4d82fc JJ |
1094 | /// Takes a path to a source file and cleans the path to it. This canonicalizes |
1095 | /// things like ".." to components which preserve the "top down" hierarchy of a | |
c34b1796 AL |
1096 | /// static HTML tree. Each component in the cleaned path will be passed as an |
1097 | /// argument to `f`. The very last component of the path (ie the file name) will | |
1098 | /// be passed to `f` if `keep_filename` is true, and ignored otherwise. | |
1a4d82fc JJ |
1099 | // FIXME (#9639): The closure should deal with &[u8] instead of &str |
1100 | // FIXME (#9639): This is too conservative, rejecting non-UTF-8 paths | |
c34b1796 | 1101 | fn clean_srcpath<F>(src_root: &Path, p: &Path, keep_filename: bool, mut f: F) where |
1a4d82fc JJ |
1102 | F: FnMut(&str), |
1103 | { | |
1a4d82fc | 1104 | // make it relative, if possible |
9cc50fc6 | 1105 | let p = p.strip_prefix(src_root).unwrap_or(p); |
1a4d82fc | 1106 | |
7453a54e SL |
1107 | let mut iter = p.components().peekable(); |
1108 | ||
c34b1796 AL |
1109 | while let Some(c) = iter.next() { |
1110 | if !keep_filename && iter.peek().is_none() { | |
1111 | break; | |
1112 | } | |
1113 | ||
7453a54e SL |
1114 | match c { |
1115 | Component::ParentDir => f("up"), | |
1116 | Component::Normal(c) => f(c.to_str().unwrap()), | |
1117 | _ => continue, | |
1a4d82fc JJ |
1118 | } |
1119 | } | |
1120 | } | |
1121 | ||
1122 | /// Attempts to find where an external crate is located, given that we're | |
1123 | /// rendering in to the specified source destination. | |
1124 | fn extern_location(e: &clean::ExternalCrate, dst: &Path) -> ExternalLocation { | |
1125 | // See if there's documentation generated into the local directory | |
85aaf69f | 1126 | let local_location = dst.join(&e.name); |
1a4d82fc JJ |
1127 | if local_location.is_dir() { |
1128 | return Local; | |
1129 | } | |
1130 | ||
1131 | // Failing that, see if there's an attribute specifying where to find this | |
1132 | // external crate | |
476ff2be SL |
1133 | e.attrs.lists("doc") |
1134 | .filter(|a| a.check_name("html_root_url")) | |
1135 | .filter_map(|a| a.value_str()) | |
1136 | .map(|url| { | |
1137 | let mut url = url.to_string(); | |
54a0048b SL |
1138 | if !url.ends_with("/") { |
1139 | url.push('/') | |
1a4d82fc | 1140 | } |
54a0048b | 1141 | Remote(url) |
476ff2be | 1142 | }).next().unwrap_or(Unknown) // Well, at least we tried. |
1a4d82fc JJ |
1143 | } |
1144 | ||
1145 | impl<'a> DocFolder for SourceCollector<'a> { | |
1146 | fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> { | |
1147 | // If we're including source files, and we haven't seen this file yet, | |
476ff2be | 1148 | // then we need to render it out to the filesystem. |
54a0048b | 1149 | if self.scx.include_sources |
ff7c6d11 XL |
1150 | // skip all invalid or macro spans |
1151 | && item.source.filename.is_real() | |
476ff2be | 1152 | // skip non-local items |
ff7c6d11 | 1153 | && item.def_id.is_local() { |
1a4d82fc JJ |
1154 | |
1155 | // If it turns out that we couldn't read this file, then we probably | |
1156 | // can't read any of the files (generating html output from json or | |
1157 | // something like that), so just don't include sources for the | |
1158 | // entire crate. The other option is maintaining this mapping on a | |
1159 | // per-file basis, but that's probably not worth it... | |
54a0048b | 1160 | self.scx |
7453a54e | 1161 | .include_sources = match self.emit_source(&item.source.filename) { |
1a4d82fc JJ |
1162 | Ok(()) => true, |
1163 | Err(e) => { | |
1164 | println!("warning: source code was requested to be rendered, \ | |
1165 | but processing `{}` had an error: {}", | |
1166 | item.source.filename, e); | |
1167 | println!(" skipping rendering of source code"); | |
1168 | false | |
1169 | } | |
1170 | }; | |
1a4d82fc | 1171 | } |
1a4d82fc JJ |
1172 | self.fold_item_recur(item) |
1173 | } | |
1174 | } | |
1175 | ||
1176 | impl<'a> SourceCollector<'a> { | |
1177 | /// Renders the given filename into its corresponding HTML source file. | |
ff7c6d11 XL |
1178 | fn emit_source(&mut self, filename: &FileName) -> io::Result<()> { |
1179 | let p = match *filename { | |
1180 | FileName::Real(ref file) => file, | |
1181 | _ => return Ok(()), | |
1182 | }; | |
1183 | if self.scx.local_sources.contains_key(&**p) { | |
54a0048b SL |
1184 | // We've already emitted this source |
1185 | return Ok(()); | |
1186 | } | |
1a4d82fc | 1187 | |
2c00a5a8 | 1188 | let contents = fs::read_string(&p)?; |
1a4d82fc JJ |
1189 | |
1190 | // Remove the utf-8 BOM if any | |
1191 | let contents = if contents.starts_with("\u{feff}") { | |
85aaf69f | 1192 | &contents[3..] |
1a4d82fc | 1193 | } else { |
2c00a5a8 | 1194 | &contents[..] |
1a4d82fc JJ |
1195 | }; |
1196 | ||
1197 | // Create the intermediate directories | |
1198 | let mut cur = self.dst.clone(); | |
62682a34 | 1199 | let mut root_path = String::from("../../"); |
54a0048b SL |
1200 | let mut href = String::new(); |
1201 | clean_srcpath(&self.scx.src_root, &p, false, |component| { | |
1a4d82fc | 1202 | cur.push(component); |
041b39d2 | 1203 | fs::create_dir_all(&cur).unwrap(); |
1a4d82fc | 1204 | root_path.push_str("../"); |
54a0048b SL |
1205 | href.push_str(component); |
1206 | href.push('/'); | |
1a4d82fc | 1207 | }); |
c34b1796 AL |
1208 | let mut fname = p.file_name().expect("source has no filename") |
1209 | .to_os_string(); | |
1210 | fname.push(".html"); | |
9e0c209e | 1211 | cur.push(&fname); |
54a0048b SL |
1212 | href.push_str(&fname.to_string_lossy()); |
1213 | ||
1214 | let mut w = BufWriter::new(File::create(&cur)?); | |
c34b1796 AL |
1215 | let title = format!("{} -- source", cur.file_name().unwrap() |
1216 | .to_string_lossy()); | |
1a4d82fc JJ |
1217 | let desc = format!("Source to the Rust file `{}`.", filename); |
1218 | let page = layout::Page { | |
85aaf69f | 1219 | title: &title, |
9e0c209e | 1220 | css_class: "source", |
85aaf69f SL |
1221 | root_path: &root_path, |
1222 | description: &desc, | |
54a0048b | 1223 | keywords: BASIC_KEYWORDS, |
1a4d82fc | 1224 | }; |
54a0048b SL |
1225 | layout::render(&mut w, &self.scx.layout, |
1226 | &page, &(""), &Source(contents), | |
2c00a5a8 XL |
1227 | self.scx.css_file_extension.is_some(), |
1228 | &self.scx.themes)?; | |
54a0048b | 1229 | w.flush()?; |
ff7c6d11 | 1230 | self.scx.local_sources.insert(p.clone(), href); |
54a0048b | 1231 | Ok(()) |
1a4d82fc JJ |
1232 | } |
1233 | } | |
1234 | ||
1235 | impl DocFolder for Cache { | |
1236 | fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> { | |
54a0048b SL |
1237 | // If this is a stripped module, |
1238 | // we don't want it or its children in the search index. | |
1239 | let orig_stripped_mod = match item.inner { | |
1240 | clean::StrippedItem(box clean::ModuleItem(..)) => { | |
a7813a04 | 1241 | mem::replace(&mut self.stripped_mod, true) |
1a4d82fc | 1242 | } |
54a0048b | 1243 | _ => self.stripped_mod, |
1a4d82fc JJ |
1244 | }; |
1245 | ||
2c00a5a8 XL |
1246 | // If the impl is from a masked crate or references something from a |
1247 | // masked crate then remove it completely. | |
1248 | if let clean::ImplItem(ref i) = item.inner { | |
1249 | if self.masked_crates.contains(&item.def_id.krate) || | |
1250 | i.trait_.def_id().map_or(false, |d| self.masked_crates.contains(&d.krate)) || | |
1251 | i.for_.def_id().map_or(false, |d| self.masked_crates.contains(&d.krate)) { | |
1252 | return None; | |
1253 | } | |
1254 | } | |
1255 | ||
1a4d82fc | 1256 | // Register any generics to their corresponding string. This is used |
476ff2be | 1257 | // when pretty-printing types. |
9e0c209e SL |
1258 | if let Some(generics) = item.inner.generics() { |
1259 | self.generics(generics); | |
1a4d82fc JJ |
1260 | } |
1261 | ||
476ff2be SL |
1262 | // Propagate a trait method's documentation to all implementors of the |
1263 | // trait. | |
1264 | if let clean::TraitItem(ref t) = item.inner { | |
1265 | self.traits.entry(item.def_id).or_insert_with(|| t.clone()); | |
1266 | } | |
1a4d82fc | 1267 | |
476ff2be SL |
1268 | // Collect all the implementors of traits. |
1269 | if let clean::ImplItem(ref i) = item.inner { | |
2c00a5a8 XL |
1270 | if let Some(did) = i.trait_.def_id() { |
1271 | self.implementors.entry(did).or_insert(vec![]).push(Impl { | |
1272 | impl_item: item.clone(), | |
1273 | }); | |
1a4d82fc JJ |
1274 | } |
1275 | } | |
1276 | ||
476ff2be | 1277 | // Index this method for searching later on. |
1a4d82fc | 1278 | if let Some(ref s) = item.name { |
9e0c209e | 1279 | let (parent, is_inherent_impl_item) = match item.inner { |
54a0048b | 1280 | clean::StrippedItem(..) => ((None, None), false), |
7453a54e SL |
1281 | clean::AssociatedConstItem(..) | |
1282 | clean::TypedefItem(_, true) if self.parent_is_trait_impl => { | |
1283 | // skip associated items in trait impls | |
1284 | ((None, None), false) | |
1285 | } | |
d9579d0f | 1286 | clean::AssociatedTypeItem(..) | |
1a4d82fc JJ |
1287 | clean::TyMethodItem(..) | |
1288 | clean::StructFieldItem(..) | | |
1289 | clean::VariantItem(..) => { | |
1290 | ((Some(*self.parent_stack.last().unwrap()), | |
85aaf69f | 1291 | Some(&self.stack[..self.stack.len() - 1])), |
1a4d82fc JJ |
1292 | false) |
1293 | } | |
9e0c209e | 1294 | clean::MethodItem(..) | clean::AssociatedConstItem(..) => { |
9346a6ac | 1295 | if self.parent_stack.is_empty() { |
1a4d82fc JJ |
1296 | ((None, None), false) |
1297 | } else { | |
1298 | let last = self.parent_stack.last().unwrap(); | |
1299 | let did = *last; | |
1300 | let path = match self.paths.get(&did) { | |
9346a6ac AL |
1301 | // The current stack not necessarily has correlation |
1302 | // for where the type was defined. On the other | |
1303 | // hand, `paths` always has the right | |
1304 | // information if present. | |
5bcae85e | 1305 | Some(&(ref fqp, ItemType::Trait)) | |
1a4d82fc | 1306 | Some(&(ref fqp, ItemType::Struct)) | |
9e0c209e | 1307 | Some(&(ref fqp, ItemType::Union)) | |
1a4d82fc | 1308 | Some(&(ref fqp, ItemType::Enum)) => |
85aaf69f SL |
1309 | Some(&fqp[..fqp.len() - 1]), |
1310 | Some(..) => Some(&*self.stack), | |
1a4d82fc JJ |
1311 | None => None |
1312 | }; | |
1313 | ((Some(*last), path), true) | |
1314 | } | |
1315 | } | |
85aaf69f | 1316 | _ => ((None, Some(&*self.stack)), false) |
1a4d82fc | 1317 | }; |
1a4d82fc JJ |
1318 | |
1319 | match parent { | |
9e0c209e | 1320 | (parent, Some(path)) if is_inherent_impl_item || (!self.stripped_mod) => { |
54a0048b | 1321 | debug_assert!(!item.is_stripped()); |
c34b1796 | 1322 | |
54a0048b SL |
1323 | // A crate has a module at its root, containing all items, |
1324 | // which should not be indexed. The crate-item itself is | |
1325 | // inserted later on when serializing the search-index. | |
9cc50fc6 SL |
1326 | if item.def_id.index != CRATE_DEF_INDEX { |
1327 | self.search_index.push(IndexItem { | |
c30ab7b3 | 1328 | ty: item.type_(), |
9cc50fc6 SL |
1329 | name: s.to_string(), |
1330 | path: path.join("::").to_string(), | |
32a655c1 | 1331 | desc: plain_summary_line(item.doc_value()), |
3b2f2976 | 1332 | parent, |
7453a54e | 1333 | parent_idx: None, |
a7813a04 | 1334 | search_type: get_index_search_type(&item), |
9cc50fc6 SL |
1335 | }); |
1336 | } | |
1a4d82fc | 1337 | } |
9e0c209e | 1338 | (Some(parent), None) if is_inherent_impl_item => { |
5bcae85e SL |
1339 | // We have a parent, but we don't know where they're |
1340 | // defined yet. Wait for later to index this item. | |
9e0c209e | 1341 | self.orphan_impl_items.push((parent, item.clone())); |
1a4d82fc JJ |
1342 | } |
1343 | _ => {} | |
1344 | } | |
1345 | } | |
1346 | ||
1347 | // Keep track of the fully qualified path for this item. | |
54a0048b SL |
1348 | let pushed = match item.name { |
1349 | Some(ref n) if !n.is_empty() => { | |
1a4d82fc JJ |
1350 | self.stack.push(n.to_string()); |
1351 | true | |
54a0048b SL |
1352 | } |
1353 | _ => false, | |
1354 | }; | |
1355 | ||
1a4d82fc JJ |
1356 | match item.inner { |
1357 | clean::StructItem(..) | clean::EnumItem(..) | | |
1358 | clean::TypedefItem(..) | clean::TraitItem(..) | | |
1359 | clean::FunctionItem(..) | clean::ModuleItem(..) | | |
3157f602 | 1360 | clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) | |
9e0c209e | 1361 | clean::ConstantItem(..) | clean::StaticItem(..) | |
2c00a5a8 | 1362 | clean::UnionItem(..) | clean::ForeignTypeItem | clean::MacroItem(..) |
3157f602 | 1363 | if !self.stripped_mod => { |
2c00a5a8 | 1364 | // Re-exported items mean that the same id can show up twice |
1a4d82fc | 1365 | // in the rustdoc ast that we're looking at. We know, |
2c00a5a8 | 1366 | // however, that a re-exported item doesn't show up in the |
1a4d82fc JJ |
1367 | // `public_items` map, so we can skip inserting into the |
1368 | // paths map if there was already an entry present and we're | |
1369 | // not a public item. | |
b039eaaf SL |
1370 | if |
1371 | !self.paths.contains_key(&item.def_id) || | |
92a42be0 | 1372 | self.access_levels.is_public(item.def_id) |
b039eaaf | 1373 | { |
1a4d82fc | 1374 | self.paths.insert(item.def_id, |
c30ab7b3 | 1375 | (self.stack.clone(), item.type_())); |
1a4d82fc JJ |
1376 | } |
1377 | } | |
476ff2be SL |
1378 | // Link variants to their parent enum because pages aren't emitted |
1379 | // for each variant. | |
54a0048b | 1380 | clean::VariantItem(..) if !self.stripped_mod => { |
1a4d82fc JJ |
1381 | let mut stack = self.stack.clone(); |
1382 | stack.pop(); | |
1383 | self.paths.insert(item.def_id, (stack, ItemType::Enum)); | |
1384 | } | |
1385 | ||
1386 | clean::PrimitiveItem(..) if item.visibility.is_some() => { | |
1387 | self.paths.insert(item.def_id, (self.stack.clone(), | |
c30ab7b3 | 1388 | item.type_())); |
1a4d82fc JJ |
1389 | } |
1390 | ||
1391 | _ => {} | |
1392 | } | |
1393 | ||
1394 | // Maintain the parent stack | |
7453a54e | 1395 | let orig_parent_is_trait_impl = self.parent_is_trait_impl; |
1a4d82fc | 1396 | let parent_pushed = match item.inner { |
abe05a73 | 1397 | clean::TraitItem(..) | clean::EnumItem(..) | clean::ForeignTypeItem | |
9e0c209e | 1398 | clean::StructItem(..) | clean::UnionItem(..) => { |
1a4d82fc | 1399 | self.parent_stack.push(item.def_id); |
7453a54e | 1400 | self.parent_is_trait_impl = false; |
1a4d82fc JJ |
1401 | true |
1402 | } | |
1403 | clean::ImplItem(ref i) => { | |
7453a54e | 1404 | self.parent_is_trait_impl = i.trait_.is_some(); |
1a4d82fc JJ |
1405 | match i.for_ { |
1406 | clean::ResolvedPath{ did, .. } => { | |
1407 | self.parent_stack.push(did); | |
1408 | true | |
1409 | } | |
9346a6ac | 1410 | ref t => { |
476ff2be SL |
1411 | let prim_did = t.primitive_type().and_then(|t| { |
1412 | self.primitive_locations.get(&t).cloned() | |
1413 | }); | |
1414 | match prim_did { | |
1415 | Some(did) => { | |
9346a6ac AL |
1416 | self.parent_stack.push(did); |
1417 | true | |
1418 | } | |
476ff2be | 1419 | None => false, |
9346a6ac AL |
1420 | } |
1421 | } | |
1a4d82fc JJ |
1422 | } |
1423 | } | |
1424 | _ => false | |
1425 | }; | |
1426 | ||
476ff2be SL |
1427 | // Once we've recursively found all the generics, hoard off all the |
1428 | // implementations elsewhere. | |
54a0048b | 1429 | let ret = self.fold_item_recur(item).and_then(|item| { |
a7813a04 | 1430 | if let clean::Item { inner: clean::ImplItem(_), .. } = item { |
54a0048b SL |
1431 | // Figure out the id of this impl. This may map to a |
1432 | // primitive rather than always to a struct/enum. | |
a7813a04 | 1433 | // Note: matching twice to restrict the lifetime of the `i` borrow. |
abe05a73 XL |
1434 | let mut dids = FxHashSet(); |
1435 | if let clean::Item { inner: clean::ImplItem(ref i), .. } = item { | |
2c00a5a8 XL |
1436 | match i.for_ { |
1437 | clean::ResolvedPath { did, .. } | | |
1438 | clean::BorrowedRef { | |
1439 | type_: box clean::ResolvedPath { did, .. }, .. | |
1440 | } => { | |
1441 | dids.insert(did); | |
1442 | } | |
1443 | ref t => { | |
1444 | let did = t.primitive_type().and_then(|t| { | |
1445 | self.primitive_locations.get(&t).cloned() | |
1446 | }); | |
abe05a73 | 1447 | |
2c00a5a8 XL |
1448 | if let Some(did) = did { |
1449 | dids.insert(did); | |
abe05a73 XL |
1450 | } |
1451 | } | |
1452 | } | |
1453 | ||
1454 | if let Some(generics) = i.trait_.as_ref().and_then(|t| t.generics()) { | |
1455 | for bound in generics { | |
1456 | if let Some(did) = bound.def_id() { | |
1457 | dids.insert(did); | |
ea8adc8c | 1458 | } |
a7813a04 | 1459 | } |
1a4d82fc | 1460 | } |
a7813a04 XL |
1461 | } else { |
1462 | unreachable!() | |
54a0048b | 1463 | }; |
abe05a73 | 1464 | for did in dids { |
476ff2be | 1465 | self.impls.entry(did).or_insert(vec![]).push(Impl { |
abe05a73 | 1466 | impl_item: item.clone(), |
476ff2be | 1467 | }); |
1a4d82fc | 1468 | } |
54a0048b SL |
1469 | None |
1470 | } else { | |
1471 | Some(item) | |
1a4d82fc | 1472 | } |
54a0048b | 1473 | }); |
1a4d82fc JJ |
1474 | |
1475 | if pushed { self.stack.pop().unwrap(); } | |
1476 | if parent_pushed { self.parent_stack.pop().unwrap(); } | |
54a0048b | 1477 | self.stripped_mod = orig_stripped_mod; |
7453a54e | 1478 | self.parent_is_trait_impl = orig_parent_is_trait_impl; |
c30ab7b3 | 1479 | ret |
1a4d82fc JJ |
1480 | } |
1481 | } | |
1482 | ||
1483 | impl<'a> Cache { | |
1484 | fn generics(&mut self, generics: &clean::Generics) { | |
ff7c6d11 XL |
1485 | for param in &generics.params { |
1486 | if let clean::GenericParam::Type(ref typ) = *param { | |
1487 | self.typarams.insert(typ.did, typ.name.clone()); | |
1488 | } | |
1a4d82fc JJ |
1489 | } |
1490 | } | |
1491 | } | |
1492 | ||
1493 | impl Context { | |
c30ab7b3 SL |
1494 | /// String representation of how to get back to the root path of the 'doc/' |
1495 | /// folder in terms of a relative URL. | |
1496 | fn root_path(&self) -> String { | |
1497 | repeat("../").take(self.current.len()).collect::<String>() | |
1498 | } | |
1499 | ||
1a4d82fc | 1500 | /// Recurse in the directory structure and change the "root path" to make |
476ff2be | 1501 | /// sure it always points to the top (relatively). |
1a4d82fc JJ |
1502 | fn recurse<T, F>(&mut self, s: String, f: F) -> T where |
1503 | F: FnOnce(&mut Context) -> T, | |
1504 | { | |
9346a6ac | 1505 | if s.is_empty() { |
1a4d82fc JJ |
1506 | panic!("Unexpected empty destination: {:?}", self.current); |
1507 | } | |
1508 | let prev = self.dst.clone(); | |
85aaf69f | 1509 | self.dst.push(&s); |
1a4d82fc JJ |
1510 | self.current.push(s); |
1511 | ||
1512 | info!("Recursing into {}", self.dst.display()); | |
1513 | ||
1a4d82fc JJ |
1514 | let ret = f(self); |
1515 | ||
1516 | info!("Recursed; leaving {}", self.dst.display()); | |
1517 | ||
1518 | // Go back to where we were at | |
1519 | self.dst = prev; | |
1a4d82fc JJ |
1520 | self.current.pop().unwrap(); |
1521 | ||
c30ab7b3 | 1522 | ret |
1a4d82fc JJ |
1523 | } |
1524 | ||
1525 | /// Main method for rendering a crate. | |
1526 | /// | |
1527 | /// This currently isn't parallelized, but it'd be pretty easy to add | |
1528 | /// parallelization to this function. | |
b039eaaf | 1529 | fn krate(self, mut krate: clean::Crate) -> Result<(), Error> { |
1a4d82fc JJ |
1530 | let mut item = match krate.module.take() { |
1531 | Some(i) => i, | |
476ff2be | 1532 | None => return Ok(()), |
1a4d82fc JJ |
1533 | }; |
1534 | item.name = Some(krate.name); | |
1535 | ||
476ff2be | 1536 | // Render the crate documentation |
c30ab7b3 | 1537 | let mut work = vec![(self, item)]; |
1a4d82fc | 1538 | |
54a0048b SL |
1539 | while let Some((mut cx, item)) = work.pop() { |
1540 | cx.item(item, |cx, item| { | |
1541 | work.push((cx.clone(), item)) | |
1542 | })? | |
1543 | } | |
1a4d82fc JJ |
1544 | Ok(()) |
1545 | } | |
1546 | ||
9e0c209e SL |
1547 | fn render_item(&self, |
1548 | writer: &mut io::Write, | |
1549 | it: &clean::Item, | |
1550 | pushname: bool) | |
1551 | -> io::Result<()> { | |
1552 | // A little unfortunate that this is done like this, but it sure | |
1553 | // does make formatting *a lot* nicer. | |
1554 | CURRENT_LOCATION_KEY.with(|slot| { | |
1555 | *slot.borrow_mut() = self.current.clone(); | |
1556 | }); | |
1a4d82fc | 1557 | |
9e0c209e SL |
1558 | let mut title = if it.is_primitive() { |
1559 | // No need to include the namespace for primitive types | |
1560 | String::new() | |
1561 | } else { | |
1562 | self.current.join("::") | |
1563 | }; | |
1564 | if pushname { | |
1565 | if !title.is_empty() { | |
1566 | title.push_str("::"); | |
1a4d82fc | 1567 | } |
9e0c209e SL |
1568 | title.push_str(it.name.as_ref().unwrap()); |
1569 | } | |
1570 | title.push_str(" - Rust"); | |
c30ab7b3 | 1571 | let tyname = it.type_().css_class(); |
9e0c209e SL |
1572 | let desc = if it.is_crate() { |
1573 | format!("API documentation for the Rust `{}` crate.", | |
1574 | self.shared.layout.krate) | |
1575 | } else { | |
1576 | format!("API documentation for the Rust `{}` {} in crate `{}`.", | |
1577 | it.name.as_ref().unwrap(), tyname, self.shared.layout.krate) | |
1578 | }; | |
1579 | let keywords = make_item_keywords(it); | |
1580 | let page = layout::Page { | |
1581 | css_class: tyname, | |
c30ab7b3 | 1582 | root_path: &self.root_path(), |
9e0c209e SL |
1583 | title: &title, |
1584 | description: &desc, | |
1585 | keywords: &keywords, | |
1586 | }; | |
1a4d82fc | 1587 | |
9e0c209e | 1588 | reset_ids(true); |
1a4d82fc | 1589 | |
9e0c209e SL |
1590 | if !self.render_redirect_pages { |
1591 | layout::render(writer, &self.shared.layout, &page, | |
1592 | &Sidebar{ cx: self, item: it }, | |
1593 | &Item{ cx: self, item: it }, | |
2c00a5a8 XL |
1594 | self.shared.css_file_extension.is_some(), |
1595 | &self.shared.themes)?; | |
9e0c209e | 1596 | } else { |
c30ab7b3 | 1597 | let mut url = self.root_path(); |
9e0c209e SL |
1598 | if let Some(&(ref names, ty)) = cache().paths.get(&it.def_id) { |
1599 | for name in &names[..names.len() - 1] { | |
1600 | url.push_str(name); | |
1601 | url.push_str("/"); | |
1a4d82fc | 1602 | } |
9e0c209e SL |
1603 | url.push_str(&item_path(ty, names.last().unwrap())); |
1604 | layout::redirect(writer, &url)?; | |
1a4d82fc | 1605 | } |
1a4d82fc | 1606 | } |
9e0c209e SL |
1607 | Ok(()) |
1608 | } | |
1a4d82fc | 1609 | |
9e0c209e SL |
1610 | /// Non-parallelized version of rendering an item. This will take the input |
1611 | /// item, render its contents, and then invoke the specified closure with | |
1612 | /// all sub-items which need to be rendered. | |
1613 | /// | |
1614 | /// The rendering driver uses this closure to queue up more work. | |
1615 | fn item<F>(&mut self, item: clean::Item, mut f: F) -> Result<(), Error> where | |
1616 | F: FnMut(&mut Context, clean::Item), | |
1617 | { | |
54a0048b SL |
1618 | // Stripped modules survive the rustdoc passes (i.e. `strip-private`) |
1619 | // if they contain impls for public types. These modules can also | |
2c00a5a8 | 1620 | // contain items such as publicly re-exported structures. |
1a4d82fc JJ |
1621 | // |
1622 | // External crates will provide links to these structures, so | |
54a0048b SL |
1623 | // these modules are recursed into, but not rendered normally |
1624 | // (a flag on the context). | |
1a4d82fc | 1625 | if !self.render_redirect_pages { |
041b39d2 | 1626 | self.render_redirect_pages = item.is_stripped(); |
1a4d82fc JJ |
1627 | } |
1628 | ||
54a0048b | 1629 | if item.is_mod() { |
1a4d82fc JJ |
1630 | // modules are special because they add a namespace. We also need to |
1631 | // recurse into the items of the module as well. | |
54a0048b SL |
1632 | let name = item.name.as_ref().unwrap().to_string(); |
1633 | let mut item = Some(item); | |
1634 | self.recurse(name, |this| { | |
1635 | let item = item.take().unwrap(); | |
3157f602 XL |
1636 | |
1637 | let mut buf = Vec::new(); | |
9e0c209e | 1638 | this.render_item(&mut buf, &item, false).unwrap(); |
3157f602 XL |
1639 | // buf will be empty if the module is stripped and there is no redirect for it |
1640 | if !buf.is_empty() { | |
abe05a73 | 1641 | try_err!(this.shared.ensure_dir(&this.dst), &this.dst); |
3157f602 | 1642 | let joint_dst = this.dst.join("index.html"); |
3157f602 XL |
1643 | let mut dst = try_err!(File::create(&joint_dst), &joint_dst); |
1644 | try_err!(dst.write_all(&buf), &joint_dst); | |
1645 | } | |
c34b1796 | 1646 | |
54a0048b SL |
1647 | let m = match item.inner { |
1648 | clean::StrippedItem(box clean::ModuleItem(m)) | | |
1649 | clean::ModuleItem(m) => m, | |
1650 | _ => unreachable!() | |
1651 | }; | |
1a4d82fc | 1652 | |
9e0c209e | 1653 | // Render sidebar-items.js used throughout this module. |
3157f602 | 1654 | if !this.render_redirect_pages { |
54a0048b SL |
1655 | let items = this.build_sidebar_items(&m); |
1656 | let js_dst = this.dst.join("sidebar-items.js"); | |
1657 | let mut js_out = BufWriter::new(try_err!(File::create(&js_dst), &js_dst)); | |
1658 | try_err!(write!(&mut js_out, "initSidebarItems({});", | |
1659 | as_json(&items)), &js_dst); | |
1660 | } | |
b039eaaf | 1661 | |
54a0048b SL |
1662 | for item in m.items { |
1663 | f(this,item); | |
1664 | } | |
9e0c209e | 1665 | |
b039eaaf | 1666 | Ok(()) |
9e0c209e | 1667 | })?; |
54a0048b | 1668 | } else if item.name.is_some() { |
3157f602 | 1669 | let mut buf = Vec::new(); |
9e0c209e | 1670 | self.render_item(&mut buf, &item, true).unwrap(); |
3157f602 XL |
1671 | // buf will be empty if the item is stripped and there is no redirect for it |
1672 | if !buf.is_empty() { | |
9e0c209e | 1673 | let name = item.name.as_ref().unwrap(); |
c30ab7b3 | 1674 | let item_type = item.type_(); |
9e0c209e | 1675 | let file_name = &item_path(item_type, name); |
abe05a73 | 1676 | try_err!(self.shared.ensure_dir(&self.dst), &self.dst); |
9e0c209e | 1677 | let joint_dst = self.dst.join(file_name); |
3157f602 XL |
1678 | let mut dst = try_err!(File::create(&joint_dst), &joint_dst); |
1679 | try_err!(dst.write_all(&buf), &joint_dst); | |
9e0c209e SL |
1680 | |
1681 | // Redirect from a sane URL using the namespace to Rustdoc's | |
1682 | // URL for the page. | |
1683 | let redir_name = format!("{}.{}.html", name, item_type.name_space()); | |
1684 | let redir_dst = self.dst.join(redir_name); | |
abe05a73 | 1685 | if let Ok(redirect_out) = OpenOptions::new().create_new(true) |
9e0c209e SL |
1686 | .write(true) |
1687 | .open(&redir_dst) { | |
abe05a73 | 1688 | let mut redirect_out = BufWriter::new(redirect_out); |
9e0c209e SL |
1689 | try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst); |
1690 | } | |
1691 | ||
1692 | // If the item is a macro, redirect from the old macro URL (with !) | |
1693 | // to the new one (without). | |
1694 | // FIXME(#35705) remove this redirect. | |
1695 | if item_type == ItemType::Macro { | |
1696 | let redir_name = format!("{}.{}!.html", item_type, name); | |
1697 | let redir_dst = self.dst.join(redir_name); | |
abe05a73 XL |
1698 | let redirect_out = try_err!(File::create(&redir_dst), &redir_dst); |
1699 | let mut redirect_out = BufWriter::new(redirect_out); | |
9e0c209e SL |
1700 | try_err!(layout::redirect(&mut redirect_out, file_name), &redir_dst); |
1701 | } | |
3157f602 | 1702 | } |
1a4d82fc | 1703 | } |
9e0c209e | 1704 | Ok(()) |
1a4d82fc JJ |
1705 | } |
1706 | ||
d9579d0f AL |
1707 | fn build_sidebar_items(&self, m: &clean::Module) -> BTreeMap<String, Vec<NameDoc>> { |
1708 | // BTreeMap instead of HashMap to get a sorted output | |
1709 | let mut map = BTreeMap::new(); | |
85aaf69f | 1710 | for item in &m.items { |
041b39d2 | 1711 | if item.is_stripped() { continue } |
1a4d82fc | 1712 | |
c30ab7b3 | 1713 | let short = item.type_().css_class(); |
1a4d82fc JJ |
1714 | let myname = match item.name { |
1715 | None => continue, | |
1716 | Some(ref s) => s.to_string(), | |
1717 | }; | |
1718 | let short = short.to_string(); | |
c34b1796 AL |
1719 | map.entry(short).or_insert(vec![]) |
1720 | .push((myname, Some(plain_summary_line(item.doc_value())))); | |
1a4d82fc JJ |
1721 | } |
1722 | ||
ff7c6d11 XL |
1723 | if self.shared.sort_modules_alphabetically { |
1724 | for (_, items) in &mut map { | |
1725 | items.sort(); | |
1726 | } | |
1a4d82fc | 1727 | } |
c30ab7b3 | 1728 | map |
1a4d82fc | 1729 | } |
1a4d82fc JJ |
1730 | } |
1731 | ||
1732 | impl<'a> Item<'a> { | |
1a4d82fc JJ |
1733 | /// Generate a url appropriate for an `href` attribute back to the source of |
1734 | /// this item. | |
1735 | /// | |
1736 | /// The url generated, when clicked, will redirect the browser back to the | |
1737 | /// original source code. | |
1738 | /// | |
1739 | /// If `None` is returned, then a source link couldn't be generated. This | |
1740 | /// may happen, for example, with externally inlined items where the source | |
1741 | /// of their crate documentation isn't known. | |
476ff2be SL |
1742 | fn src_href(&self) -> Option<String> { |
1743 | let mut root = self.cx.root_path(); | |
d9579d0f | 1744 | |
476ff2be SL |
1745 | let cache = cache(); |
1746 | let mut path = String::new(); | |
ff7c6d11 XL |
1747 | |
1748 | // We can safely ignore macros from other libraries | |
1749 | let file = match self.item.source.filename { | |
1750 | FileName::Real(ref path) => path, | |
1751 | _ => return None, | |
1752 | }; | |
1753 | ||
476ff2be | 1754 | let (krate, path) = if self.item.def_id.is_local() { |
ff7c6d11 | 1755 | if let Some(path) = self.cx.shared.local_sources.get(file) { |
476ff2be SL |
1756 | (&self.cx.shared.layout.krate, path) |
1757 | } else { | |
1758 | return None; | |
1759 | } | |
1a4d82fc | 1760 | } else { |
476ff2be SL |
1761 | let (krate, src_root) = match cache.extern_locations.get(&self.item.def_id.krate) { |
1762 | Some(&(ref name, ref src, Local)) => (name, src), | |
1763 | Some(&(ref name, ref src, Remote(ref s))) => { | |
1764 | root = s.to_string(); | |
1765 | (name, src) | |
1766 | } | |
1767 | Some(&(_, _, Unknown)) | None => return None, | |
1768 | }; | |
1769 | ||
476ff2be SL |
1770 | clean_srcpath(&src_root, file, false, |component| { |
1771 | path.push_str(component); | |
1772 | path.push('/'); | |
1773 | }); | |
1774 | let mut fname = file.file_name().expect("source has no filename") | |
1775 | .to_os_string(); | |
1776 | fname.push(".html"); | |
1777 | path.push_str(&fname.to_string_lossy()); | |
1778 | (krate, &path) | |
1779 | }; | |
1780 | ||
1781 | let lines = if self.item.source.loline == self.item.source.hiline { | |
1782 | format!("{}", self.item.source.loline) | |
1783 | } else { | |
1784 | format!("{}-{}", self.item.source.loline, self.item.source.hiline) | |
1785 | }; | |
1786 | Some(format!("{root}src/{krate}/{path}#{lines}", | |
abe05a73 | 1787 | root = Escape(&root), |
476ff2be SL |
1788 | krate = krate, |
1789 | path = path, | |
1790 | lines = lines)) | |
1a4d82fc JJ |
1791 | } |
1792 | } | |
1793 | ||
85aaf69f | 1794 | impl<'a> fmt::Display for Item<'a> { |
1a4d82fc | 1795 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
54a0048b | 1796 | debug_assert!(!self.item.is_stripped()); |
1a4d82fc | 1797 | // Write the breadcrumb trail header for the top |
54a0048b | 1798 | write!(fmt, "\n<h1 class='fqn'><span class='in-band'>")?; |
1a4d82fc JJ |
1799 | match self.item.inner { |
1800 | clean::ModuleItem(ref m) => if m.is_crate { | |
54a0048b | 1801 | write!(fmt, "Crate ")?; |
1a4d82fc | 1802 | } else { |
54a0048b | 1803 | write!(fmt, "Module ")?; |
1a4d82fc | 1804 | }, |
ea8adc8c | 1805 | clean::FunctionItem(..) | clean::ForeignFunctionItem(..) => write!(fmt, "Function ")?, |
54a0048b SL |
1806 | clean::TraitItem(..) => write!(fmt, "Trait ")?, |
1807 | clean::StructItem(..) => write!(fmt, "Struct ")?, | |
9e0c209e | 1808 | clean::UnionItem(..) => write!(fmt, "Union ")?, |
54a0048b | 1809 | clean::EnumItem(..) => write!(fmt, "Enum ")?, |
9e0c209e SL |
1810 | clean::TypedefItem(..) => write!(fmt, "Type Definition ")?, |
1811 | clean::MacroItem(..) => write!(fmt, "Macro ")?, | |
54a0048b | 1812 | clean::PrimitiveItem(..) => write!(fmt, "Primitive Type ")?, |
ea8adc8c | 1813 | clean::StaticItem(..) | clean::ForeignStaticItem(..) => write!(fmt, "Static ")?, |
9e0c209e | 1814 | clean::ConstantItem(..) => write!(fmt, "Constant ")?, |
abe05a73 | 1815 | clean::ForeignTypeItem => write!(fmt, "Foreign Type ")?, |
9e0c209e SL |
1816 | _ => { |
1817 | // We don't generate pages for any other type. | |
1818 | unreachable!(); | |
1819 | } | |
1a4d82fc | 1820 | } |
5bcae85e | 1821 | if !self.item.is_primitive() { |
85aaf69f | 1822 | let cur = &self.cx.current; |
54a0048b | 1823 | let amt = if self.item.is_mod() { cur.len() - 1 } else { cur.len() }; |
1a4d82fc | 1824 | for (i, component) in cur.iter().enumerate().take(amt) { |
54a0048b SL |
1825 | write!(fmt, "<a href='{}index.html'>{}</a>::<wbr>", |
1826 | repeat("../").take(cur.len() - i - 1) | |
1827 | .collect::<String>(), | |
1828 | component)?; | |
1a4d82fc JJ |
1829 | } |
1830 | } | |
8bb4bdeb | 1831 | write!(fmt, "<a class=\"{}\" href=''>{}</a>", |
c30ab7b3 | 1832 | self.item.type_(), self.item.name.as_ref().unwrap())?; |
1a4d82fc | 1833 | |
54a0048b SL |
1834 | write!(fmt, "</span>")?; // in-band |
1835 | write!(fmt, "<span class='out-of-band'>")?; | |
a7813a04 XL |
1836 | if let Some(version) = self.item.stable_since() { |
1837 | write!(fmt, "<span class='since' title='Stable since Rust version {0}'>{0}</span>", | |
1838 | version)?; | |
1839 | } | |
54a0048b SL |
1840 | write!(fmt, |
1841 | r##"<span id='render-detail'> | |
1842 | <a id="toggle-all-docs" href="javascript:void(0)" title="collapse all docs"> | |
1843 | [<span class='inner'>−</span>] | |
1844 | </a> | |
1845 | </span>"##)?; | |
1a4d82fc JJ |
1846 | |
1847 | // Write `src` tag | |
1848 | // | |
1849 | // When this item is part of a `pub use` in a downstream crate, the | |
1850 | // [src] link in the downstream documentation will actually come back to | |
1851 | // this page, and this link will be auto-clicked. The `id` attribute is | |
1852 | // used to find the link to auto-click. | |
5bcae85e | 1853 | if self.cx.shared.include_sources && !self.item.is_primitive() { |
476ff2be SL |
1854 | if let Some(l) = self.src_href() { |
1855 | write!(fmt, "<a class='srclink' href='{}' title='{}'>[src]</a>", | |
1856 | l, "goto source code")?; | |
1a4d82fc JJ |
1857 | } |
1858 | } | |
1859 | ||
54a0048b | 1860 | write!(fmt, "</span>")?; // out-of-band |
1a4d82fc | 1861 | |
54a0048b | 1862 | write!(fmt, "</h1>\n")?; |
1a4d82fc JJ |
1863 | |
1864 | match self.item.inner { | |
1865 | clean::ModuleItem(ref m) => { | |
85aaf69f | 1866 | item_module(fmt, self.cx, self.item, &m.items) |
1a4d82fc JJ |
1867 | } |
1868 | clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f) => | |
e9174d1e | 1869 | item_function(fmt, self.cx, self.item, f), |
1a4d82fc | 1870 | clean::TraitItem(ref t) => item_trait(fmt, self.cx, self.item, t), |
e9174d1e | 1871 | clean::StructItem(ref s) => item_struct(fmt, self.cx, self.item, s), |
9e0c209e | 1872 | clean::UnionItem(ref s) => item_union(fmt, self.cx, self.item, s), |
e9174d1e SL |
1873 | clean::EnumItem(ref e) => item_enum(fmt, self.cx, self.item, e), |
1874 | clean::TypedefItem(ref t, _) => item_typedef(fmt, self.cx, self.item, t), | |
1875 | clean::MacroItem(ref m) => item_macro(fmt, self.cx, self.item, m), | |
1876 | clean::PrimitiveItem(ref p) => item_primitive(fmt, self.cx, self.item, p), | |
1a4d82fc | 1877 | clean::StaticItem(ref i) | clean::ForeignStaticItem(ref i) => |
e9174d1e SL |
1878 | item_static(fmt, self.cx, self.item, i), |
1879 | clean::ConstantItem(ref c) => item_constant(fmt, self.cx, self.item, c), | |
abe05a73 | 1880 | clean::ForeignTypeItem => item_foreign_type(fmt, self.cx, self.item), |
9e0c209e SL |
1881 | _ => { |
1882 | // We don't generate pages for any other type. | |
1883 | unreachable!(); | |
1884 | } | |
1a4d82fc JJ |
1885 | } |
1886 | } | |
1887 | } | |
1888 | ||
3157f602 XL |
1889 | fn item_path(ty: ItemType, name: &str) -> String { |
1890 | match ty { | |
1891 | ItemType::Module => format!("{}/index.html", name), | |
9e0c209e | 1892 | _ => format!("{}.{}.html", ty.css_class(), name), |
1a4d82fc JJ |
1893 | } |
1894 | } | |
1895 | ||
1896 | fn full_path(cx: &Context, item: &clean::Item) -> String { | |
c1a9b12d | 1897 | let mut s = cx.current.join("::"); |
1a4d82fc | 1898 | s.push_str("::"); |
85aaf69f | 1899 | s.push_str(item.name.as_ref().unwrap()); |
c30ab7b3 | 1900 | s |
1a4d82fc JJ |
1901 | } |
1902 | ||
c34b1796 | 1903 | fn shorter<'a>(s: Option<&'a str>) -> String { |
1a4d82fc | 1904 | match s { |
ff7c6d11 XL |
1905 | Some(s) => s.lines() |
1906 | .skip_while(|s| s.chars().all(|c| c.is_whitespace())) | |
1907 | .take_while(|line|{ | |
c34b1796 AL |
1908 | (*line).chars().any(|chr|{ |
1909 | !chr.is_whitespace() | |
1910 | }) | |
c1a9b12d | 1911 | }).collect::<Vec<_>>().join("\n"), |
c34b1796 | 1912 | None => "".to_string() |
1a4d82fc JJ |
1913 | } |
1914 | } | |
1915 | ||
85aaf69f | 1916 | #[inline] |
c34b1796 AL |
1917 | fn plain_summary_line(s: Option<&str>) -> String { |
1918 | let line = shorter(s).replace("\n", " "); | |
1919 | markdown::plain_summary_line(&line[..]) | |
85aaf69f SL |
1920 | } |
1921 | ||
e9174d1e | 1922 | fn document(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item) -> fmt::Result { |
ff7c6d11 XL |
1923 | if let Some(ref name) = item.name { |
1924 | info!("Documenting {}", name); | |
1925 | } | |
3157f602 | 1926 | document_stability(w, cx, item)?; |
041b39d2 | 1927 | let prefix = render_assoc_const_value(item); |
ea8adc8c | 1928 | document_full(w, item, cx, &prefix)?; |
1a4d82fc JJ |
1929 | Ok(()) |
1930 | } | |
1931 | ||
ea8adc8c XL |
1932 | /// Render md_text as markdown. Warns the user if there are difference in |
1933 | /// rendering between Pulldown and Hoedown. | |
1934 | fn render_markdown(w: &mut fmt::Formatter, | |
1935 | md_text: &str, | |
2c00a5a8 | 1936 | links: Vec<(String, String)>, |
ea8adc8c XL |
1937 | span: Span, |
1938 | render_type: RenderType, | |
1939 | prefix: &str, | |
1940 | scx: &SharedContext) | |
1941 | -> fmt::Result { | |
2c00a5a8 XL |
1942 | let (hoedown_output, pulldown_output) = |
1943 | render_text(|ty| format!("{}", Markdown(md_text, &links, ty))); | |
abe05a73 XL |
1944 | let mut differences = html_diff::get_differences(&pulldown_output, &hoedown_output); |
1945 | differences.retain(|s| { | |
1946 | match *s { | |
1947 | html_diff::Difference::NodeText { ref elem_text, | |
1948 | ref opposite_elem_text, | |
1949 | .. } | |
1950 | if elem_text.split_whitespace().eq(opposite_elem_text.split_whitespace()) => { | |
1951 | false | |
ea8adc8c | 1952 | } |
abe05a73 | 1953 | _ => true, |
ea8adc8c | 1954 | } |
abe05a73 | 1955 | }); |
ea8adc8c | 1956 | |
abe05a73 XL |
1957 | if !differences.is_empty() { |
1958 | scx.markdown_warnings.borrow_mut().push((span, md_text.to_owned(), differences)); | |
1959 | } | |
ea8adc8c | 1960 | |
abe05a73 XL |
1961 | write!(w, "<div class='docblock'>{}{}</div>", |
1962 | prefix, | |
1963 | if render_type == RenderType::Pulldown { pulldown_output } else { hoedown_output }) | |
ea8adc8c XL |
1964 | } |
1965 | ||
cc61c64b | 1966 | fn document_short(w: &mut fmt::Formatter, item: &clean::Item, link: AssocItemLink, |
ea8adc8c | 1967 | cx: &Context, prefix: &str) -> fmt::Result { |
a7813a04 XL |
1968 | if let Some(s) = item.doc_value() { |
1969 | let markdown = if s.contains('\n') { | |
1970 | format!("{} [Read more]({})", | |
1971 | &plain_summary_line(Some(s)), naive_assoc_href(item, link)) | |
1972 | } else { | |
1973 | format!("{}", &plain_summary_line(Some(s))) | |
1974 | }; | |
2c00a5a8 XL |
1975 | render_markdown(w, |
1976 | &markdown, | |
1977 | item.links(), | |
1978 | item.source.clone(), | |
1979 | cx.render_type, | |
1980 | prefix, | |
1981 | &cx.shared)?; | |
041b39d2 XL |
1982 | } else if !prefix.is_empty() { |
1983 | write!(w, "<div class='docblock'>{}</div>", prefix)?; | |
a7813a04 XL |
1984 | } |
1985 | Ok(()) | |
1986 | } | |
1987 | ||
041b39d2 | 1988 | fn render_assoc_const_value(item: &clean::Item) -> String { |
8bb4bdeb | 1989 | match item.inner { |
041b39d2 XL |
1990 | clean::AssociatedConstItem(ref ty, Some(ref default)) => { |
1991 | highlight::render_with_highlighting( | |
1992 | &format!("{}: {:#} = {}", item.name.as_ref().unwrap(), ty, default), | |
1993 | None, | |
1994 | None, | |
1995 | None, | |
ea8adc8c | 1996 | None, |
041b39d2 | 1997 | ) |
8bb4bdeb XL |
1998 | } |
1999 | _ => String::new(), | |
2000 | } | |
2001 | } | |
2002 | ||
cc61c64b | 2003 | fn document_full(w: &mut fmt::Formatter, item: &clean::Item, |
ea8adc8c | 2004 | cx: &Context, prefix: &str) -> fmt::Result { |
ff7c6d11 XL |
2005 | if let Some(s) = cx.shared.maybe_collapsed_doc_value(item) { |
2006 | debug!("Doc block: =====\n{}\n=====", s); | |
2c00a5a8 XL |
2007 | render_markdown(w, |
2008 | &*s, | |
2009 | item.links(), | |
2010 | item.source.clone(), | |
2011 | cx.render_type, | |
2012 | prefix, | |
2013 | &cx.shared)?; | |
041b39d2 XL |
2014 | } else if !prefix.is_empty() { |
2015 | write!(w, "<div class='docblock'>{}</div>", prefix)?; | |
3157f602 XL |
2016 | } |
2017 | Ok(()) | |
2018 | } | |
2019 | ||
2020 | fn document_stability(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item) -> fmt::Result { | |
32a655c1 SL |
2021 | let stabilities = short_stability(item, cx, true); |
2022 | if !stabilities.is_empty() { | |
2023 | write!(w, "<div class='stability'>")?; | |
2024 | for stability in stabilities { | |
2025 | write!(w, "{}", stability)?; | |
2026 | } | |
2027 | write!(w, "</div>")?; | |
3157f602 XL |
2028 | } |
2029 | Ok(()) | |
2030 | } | |
2031 | ||
cc61c64b XL |
2032 | fn name_key(name: &str) -> (&str, u64, usize) { |
2033 | // find number at end | |
2034 | let split = name.bytes().rposition(|b| b < b'0' || b'9' < b).map_or(0, |s| s + 1); | |
2035 | ||
2036 | // count leading zeroes | |
2037 | let after_zeroes = | |
2038 | name[split..].bytes().position(|b| b != b'0').map_or(name.len(), |extra| split + extra); | |
2039 | ||
2040 | // sort leading zeroes last | |
2041 | let num_zeroes = after_zeroes - split; | |
2042 | ||
2043 | match name[split..].parse() { | |
2044 | Ok(n) => (&name[..split], n, num_zeroes), | |
2045 | Err(_) => (name, 0, num_zeroes), | |
2046 | } | |
2047 | } | |
2048 | ||
1a4d82fc JJ |
2049 | fn item_module(w: &mut fmt::Formatter, cx: &Context, |
2050 | item: &clean::Item, items: &[clean::Item]) -> fmt::Result { | |
54a0048b | 2051 | document(w, cx, item)?; |
1a4d82fc | 2052 | |
2c00a5a8 XL |
2053 | let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()) |
2054 | .collect::<Vec<usize>>(); | |
1a4d82fc JJ |
2055 | |
2056 | // the order of item types in the listing | |
2057 | fn reorder(ty: ItemType) -> u8 { | |
2058 | match ty { | |
85aaf69f SL |
2059 | ItemType::ExternCrate => 0, |
2060 | ItemType::Import => 1, | |
2061 | ItemType::Primitive => 2, | |
2062 | ItemType::Module => 3, | |
2063 | ItemType::Macro => 4, | |
2064 | ItemType::Struct => 5, | |
2065 | ItemType::Enum => 6, | |
2066 | ItemType::Constant => 7, | |
2067 | ItemType::Static => 8, | |
2068 | ItemType::Trait => 9, | |
2069 | ItemType::Function => 10, | |
2070 | ItemType::Typedef => 12, | |
9e0c209e SL |
2071 | ItemType::Union => 13, |
2072 | _ => 14 + ty as u8, | |
1a4d82fc JJ |
2073 | } |
2074 | } | |
2075 | ||
c34b1796 | 2076 | fn cmp(i1: &clean::Item, i2: &clean::Item, idx1: usize, idx2: usize) -> Ordering { |
c30ab7b3 SL |
2077 | let ty1 = i1.type_(); |
2078 | let ty2 = i2.type_(); | |
d9579d0f AL |
2079 | if ty1 != ty2 { |
2080 | return (reorder(ty1), idx1).cmp(&(reorder(ty2), idx2)) | |
2081 | } | |
2082 | let s1 = i1.stability.as_ref().map(|s| s.level); | |
2083 | let s2 = i2.stability.as_ref().map(|s| s.level); | |
2084 | match (s1, s2) { | |
b039eaaf SL |
2085 | (Some(stability::Unstable), Some(stability::Stable)) => return Ordering::Greater, |
2086 | (Some(stability::Stable), Some(stability::Unstable)) => return Ordering::Less, | |
d9579d0f | 2087 | _ => {} |
1a4d82fc | 2088 | } |
cc61c64b XL |
2089 | let lhs = i1.name.as_ref().map_or("", |s| &**s); |
2090 | let rhs = i2.name.as_ref().map_or("", |s| &**s); | |
2091 | name_key(lhs).cmp(&name_key(rhs)) | |
1a4d82fc JJ |
2092 | } |
2093 | ||
ff7c6d11 XL |
2094 | if cx.shared.sort_modules_alphabetically { |
2095 | indices.sort_by(|&i1, &i2| cmp(&items[i1], &items[i2], i1, i2)); | |
2096 | } | |
2c00a5a8 | 2097 | // This call is to remove re-export duplicates in cases such as: |
3b2f2976 XL |
2098 | // |
2099 | // ``` | |
2100 | // pub mod foo { | |
2101 | // pub mod bar { | |
2102 | // pub trait Double { fn foo(); } | |
2103 | // } | |
2104 | // } | |
2105 | // | |
2106 | // pub use foo::bar::*; | |
2107 | // pub use foo::*; | |
2108 | // ``` | |
2109 | // | |
2110 | // `Double` will appear twice in the generated docs. | |
2111 | // | |
2112 | // FIXME: This code is quite ugly and could be improved. Small issue: DefId | |
2113 | // can be identical even if the elements are different (mostly in imports). | |
2114 | // So in case this is an import, we keep everything by adding a "unique id" | |
2115 | // (which is the position in the vector). | |
2116 | indices.dedup_by_key(|i| (items[*i].def_id, | |
2117 | if items[*i].name.as_ref().is_some() { | |
2118 | Some(full_path(cx, &items[*i]).clone()) | |
2119 | } else { | |
2120 | None | |
2121 | }, | |
2122 | items[*i].type_(), | |
2123 | if items[*i].is_import() { | |
2124 | *i | |
2125 | } else { | |
2126 | 0 | |
2127 | })); | |
1a4d82fc JJ |
2128 | |
2129 | debug!("{:?}", indices); | |
2130 | let mut curty = None; | |
85aaf69f | 2131 | for &idx in &indices { |
1a4d82fc | 2132 | let myitem = &items[idx]; |
54a0048b SL |
2133 | if myitem.is_stripped() { |
2134 | continue; | |
2135 | } | |
1a4d82fc | 2136 | |
c30ab7b3 | 2137 | let myty = Some(myitem.type_()); |
85aaf69f SL |
2138 | if curty == Some(ItemType::ExternCrate) && myty == Some(ItemType::Import) { |
2139 | // Put `extern crate` and `use` re-exports in the same section. | |
2140 | curty = myty; | |
2141 | } else if myty != curty { | |
1a4d82fc | 2142 | if curty.is_some() { |
54a0048b | 2143 | write!(w, "</table>")?; |
1a4d82fc JJ |
2144 | } |
2145 | curty = myty; | |
2146 | let (short, name) = match myty.unwrap() { | |
85aaf69f | 2147 | ItemType::ExternCrate | |
2c00a5a8 | 2148 | ItemType::Import => ("reexports", "Re-exports"), |
1a4d82fc JJ |
2149 | ItemType::Module => ("modules", "Modules"), |
2150 | ItemType::Struct => ("structs", "Structs"), | |
9e0c209e | 2151 | ItemType::Union => ("unions", "Unions"), |
1a4d82fc JJ |
2152 | ItemType::Enum => ("enums", "Enums"), |
2153 | ItemType::Function => ("functions", "Functions"), | |
2154 | ItemType::Typedef => ("types", "Type Definitions"), | |
2155 | ItemType::Static => ("statics", "Statics"), | |
2156 | ItemType::Constant => ("constants", "Constants"), | |
2157 | ItemType::Trait => ("traits", "Traits"), | |
2158 | ItemType::Impl => ("impls", "Implementations"), | |
1a4d82fc JJ |
2159 | ItemType::TyMethod => ("tymethods", "Type Methods"), |
2160 | ItemType::Method => ("methods", "Methods"), | |
2161 | ItemType::StructField => ("fields", "Struct Fields"), | |
2162 | ItemType::Variant => ("variants", "Variants"), | |
2163 | ItemType::Macro => ("macros", "Macros"), | |
2164 | ItemType::Primitive => ("primitives", "Primitive Types"), | |
2165 | ItemType::AssociatedType => ("associated-types", "Associated Types"), | |
d9579d0f | 2166 | ItemType::AssociatedConst => ("associated-consts", "Associated Constants"), |
abe05a73 | 2167 | ItemType::ForeignType => ("foreign-types", "Foreign Types"), |
1a4d82fc | 2168 | }; |
54a0048b SL |
2169 | write!(w, "<h2 id='{id}' class='section-header'>\ |
2170 | <a href=\"#{id}\">{name}</a></h2>\n<table>", | |
2171 | id = derive_id(short.to_owned()), name = name)?; | |
1a4d82fc JJ |
2172 | } |
2173 | ||
2174 | match myitem.inner { | |
85aaf69f | 2175 | clean::ExternCrateItem(ref name, ref src) => { |
a7813a04 XL |
2176 | use html::format::HRef; |
2177 | ||
85aaf69f SL |
2178 | match *src { |
2179 | Some(ref src) => { | |
54a0048b SL |
2180 | write!(w, "<tr><td><code>{}extern crate {} as {};", |
2181 | VisSpace(&myitem.visibility), | |
a7813a04 | 2182 | HRef::new(myitem.def_id, src), |
54a0048b | 2183 | name)? |
1a4d82fc | 2184 | } |
85aaf69f | 2185 | None => { |
54a0048b | 2186 | write!(w, "<tr><td><code>{}extern crate {};", |
a7813a04 XL |
2187 | VisSpace(&myitem.visibility), |
2188 | HRef::new(myitem.def_id, name))? | |
1a4d82fc JJ |
2189 | } |
2190 | } | |
54a0048b | 2191 | write!(w, "</code></td></tr>")?; |
85aaf69f | 2192 | } |
1a4d82fc | 2193 | |
85aaf69f | 2194 | clean::ImportItem(ref import) => { |
54a0048b SL |
2195 | write!(w, "<tr><td><code>{}{}</code></td></tr>", |
2196 | VisSpace(&myitem.visibility), *import)?; | |
1a4d82fc JJ |
2197 | } |
2198 | ||
2199 | _ => { | |
2200 | if myitem.name.is_none() { continue } | |
a7813a04 XL |
2201 | |
2202 | let stabilities = short_stability(myitem, cx, false); | |
2203 | ||
2204 | let stab_docs = if !stabilities.is_empty() { | |
2205 | stabilities.iter() | |
2206 | .map(|s| format!("[{}]", s)) | |
2207 | .collect::<Vec<_>>() | |
2208 | .as_slice() | |
2209 | .join(" ") | |
d9579d0f AL |
2210 | } else { |
2211 | String::new() | |
2212 | }; | |
476ff2be | 2213 | |
8bb4bdeb XL |
2214 | let unsafety_flag = match myitem.inner { |
2215 | clean::FunctionItem(ref func) | clean::ForeignFunctionItem(ref func) | |
2216 | if func.unsafety == hir::Unsafety::Unsafe => { | |
2217 | "<a title='unsafe function' href='#'><sup>âš </sup></a>" | |
476ff2be | 2218 | } |
8bb4bdeb XL |
2219 | _ => "", |
2220 | }; | |
476ff2be | 2221 | |
7453a54e | 2222 | let doc_value = myitem.doc_value().unwrap_or(""); |
54a0048b SL |
2223 | write!(w, " |
2224 | <tr class='{stab} module-item'> | |
8bb4bdeb XL |
2225 | <td><a class=\"{class}\" href=\"{href}\" |
2226 | title='{title_type} {title}'>{name}</a>{unsafety_flag}</td> | |
9e0c209e | 2227 | <td class='docblock-short'> |
54a0048b SL |
2228 | {stab_docs} {docs} |
2229 | </td> | |
2230 | </tr>", | |
2231 | name = *myitem.name.as_ref().unwrap(), | |
2232 | stab_docs = stab_docs, | |
cc61c64b XL |
2233 | docs = if cx.render_type == RenderType::Hoedown { |
2234 | format!("{}", | |
2c00a5a8 | 2235 | shorter(Some(&Markdown(doc_value, &myitem.links(), |
cc61c64b XL |
2236 | RenderType::Hoedown).to_string()))) |
2237 | } else { | |
2c00a5a8 | 2238 | format!("{}", MarkdownSummaryLine(doc_value, &myitem.links())) |
cc61c64b | 2239 | }, |
c30ab7b3 | 2240 | class = myitem.type_(), |
8bb4bdeb | 2241 | stab = myitem.stability_class().unwrap_or("".to_string()), |
476ff2be | 2242 | unsafety_flag = unsafety_flag, |
c30ab7b3 | 2243 | href = item_path(myitem.type_(), myitem.name.as_ref().unwrap()), |
8bb4bdeb | 2244 | title_type = myitem.type_(), |
54a0048b | 2245 | title = full_path(cx, myitem))?; |
1a4d82fc JJ |
2246 | } |
2247 | } | |
2248 | } | |
2249 | ||
3157f602 XL |
2250 | if curty.is_some() { |
2251 | write!(w, "</table>")?; | |
2252 | } | |
2253 | Ok(()) | |
1a4d82fc JJ |
2254 | } |
2255 | ||
a7813a04 XL |
2256 | fn short_stability(item: &clean::Item, cx: &Context, show_reason: bool) -> Vec<String> { |
2257 | let mut stability = vec![]; | |
2258 | ||
2259 | if let Some(stab) = item.stability.as_ref() { | |
476ff2be SL |
2260 | let deprecated_reason = if show_reason && !stab.deprecated_reason.is_empty() { |
2261 | format!(": {}", stab.deprecated_reason) | |
d9579d0f AL |
2262 | } else { |
2263 | String::new() | |
2264 | }; | |
a7813a04 | 2265 | if !stab.deprecated_since.is_empty() { |
d9579d0f AL |
2266 | let since = if show_reason { |
2267 | format!(" since {}", Escape(&stab.deprecated_since)) | |
2268 | } else { | |
2269 | String::new() | |
2270 | }; | |
cc61c64b XL |
2271 | let text = format!("Deprecated{}{}", |
2272 | since, | |
2273 | MarkdownHtml(&deprecated_reason, cx.render_type)); | |
32a655c1 | 2274 | stability.push(format!("<div class='stab deprecated'>{}</div>", text)) |
a7813a04 XL |
2275 | }; |
2276 | ||
2277 | if stab.level == stability::Unstable { | |
32a655c1 SL |
2278 | if show_reason { |
2279 | let unstable_extra = match (!stab.feature.is_empty(), | |
2280 | &cx.shared.issue_tracker_base_url, | |
2281 | stab.issue) { | |
9cc50fc6 | 2282 | (true, &Some(ref tracker_url), Some(issue_no)) if issue_no > 0 => |
8bb4bdeb | 2283 | format!(" (<code>{} </code><a href=\"{}{}\">#{}</a>)", |
e9174d1e | 2284 | Escape(&stab.feature), tracker_url, issue_no, issue_no), |
9cc50fc6 | 2285 | (false, &Some(ref tracker_url), Some(issue_no)) if issue_no > 0 => |
e9174d1e SL |
2286 | format!(" (<a href=\"{}{}\">#{}</a>)", Escape(&tracker_url), issue_no, |
2287 | issue_no), | |
9e0c209e | 2288 | (true, ..) => |
e9174d1e SL |
2289 | format!(" (<code>{}</code>)", Escape(&stab.feature)), |
2290 | _ => String::new(), | |
32a655c1 SL |
2291 | }; |
2292 | if stab.unstable_reason.is_empty() { | |
2293 | stability.push(format!("<div class='stab unstable'>\ | |
2294 | <span class=microscope>🔬</span> \ | |
8bb4bdeb | 2295 | This is a nightly-only experimental API. {}\ |
32a655c1 | 2296 | </div>", |
8bb4bdeb | 2297 | unstable_extra)); |
32a655c1 SL |
2298 | } else { |
2299 | let text = format!("<summary><span class=microscope>🔬</span> \ | |
8bb4bdeb | 2300 | This is a nightly-only experimental API. {}\ |
32a655c1 | 2301 | </summary>{}", |
cc61c64b XL |
2302 | unstable_extra, |
2303 | MarkdownHtml(&stab.unstable_reason, cx.render_type)); | |
32a655c1 SL |
2304 | stability.push(format!("<div class='stab unstable'><details>{}</details></div>", |
2305 | text)); | |
e9174d1e SL |
2306 | } |
2307 | } else { | |
32a655c1 SL |
2308 | stability.push(format!("<div class='stab unstable'>Experimental</div>")) |
2309 | } | |
a7813a04 XL |
2310 | }; |
2311 | } else if let Some(depr) = item.deprecation.as_ref() { | |
2312 | let note = if show_reason && !depr.note.is_empty() { | |
2313 | format!(": {}", depr.note) | |
d9579d0f | 2314 | } else { |
a7813a04 XL |
2315 | String::new() |
2316 | }; | |
2317 | let since = if show_reason && !depr.since.is_empty() { | |
2318 | format!(" since {}", Escape(&depr.since)) | |
2319 | } else { | |
2320 | String::new() | |
d9579d0f | 2321 | }; |
9cc50fc6 | 2322 | |
cc61c64b | 2323 | let text = format!("Deprecated{}{}", since, MarkdownHtml(¬e, cx.render_type)); |
32a655c1 | 2324 | stability.push(format!("<div class='stab deprecated'>{}</div>", text)) |
a7813a04 XL |
2325 | } |
2326 | ||
3b2f2976 XL |
2327 | if let Some(ref cfg) = item.attrs.cfg { |
2328 | stability.push(format!("<div class='stab portability'>{}</div>", if show_reason { | |
2329 | cfg.render_long_html() | |
2330 | } else { | |
2331 | cfg.render_short_html() | |
2332 | })); | |
2333 | } | |
2334 | ||
a7813a04 | 2335 | stability |
d9579d0f AL |
2336 | } |
2337 | ||
1a4d82fc JJ |
2338 | struct Initializer<'a>(&'a str); |
2339 | ||
85aaf69f | 2340 | impl<'a> fmt::Display for Initializer<'a> { |
1a4d82fc JJ |
2341 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
2342 | let Initializer(s) = *self; | |
9346a6ac | 2343 | if s.is_empty() { return Ok(()); } |
54a0048b | 2344 | write!(f, "<code> = </code>")?; |
a7813a04 | 2345 | write!(f, "<code>{}</code>", Escape(s)) |
1a4d82fc JJ |
2346 | } |
2347 | } | |
2348 | ||
e9174d1e | 2349 | fn item_constant(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, |
1a4d82fc | 2350 | c: &clean::Constant) -> fmt::Result { |
8bb4bdeb XL |
2351 | write!(w, "<pre class='rust const'>")?; |
2352 | render_attributes(w, it)?; | |
2353 | write!(w, "{vis}const \ | |
54a0048b SL |
2354 | {name}: {typ}{init}</pre>", |
2355 | vis = VisSpace(&it.visibility), | |
85aaf69f | 2356 | name = it.name.as_ref().unwrap(), |
1a4d82fc | 2357 | typ = c.type_, |
54a0048b | 2358 | init = Initializer(&c.expr))?; |
e9174d1e | 2359 | document(w, cx, it) |
1a4d82fc JJ |
2360 | } |
2361 | ||
e9174d1e | 2362 | fn item_static(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, |
1a4d82fc | 2363 | s: &clean::Static) -> fmt::Result { |
8bb4bdeb XL |
2364 | write!(w, "<pre class='rust static'>")?; |
2365 | render_attributes(w, it)?; | |
2366 | write!(w, "{vis}static {mutability}\ | |
54a0048b SL |
2367 | {name}: {typ}{init}</pre>", |
2368 | vis = VisSpace(&it.visibility), | |
1a4d82fc | 2369 | mutability = MutableSpace(s.mutability), |
85aaf69f | 2370 | name = it.name.as_ref().unwrap(), |
1a4d82fc | 2371 | typ = s.type_, |
54a0048b | 2372 | init = Initializer(&s.expr))?; |
e9174d1e | 2373 | document(w, cx, it) |
1a4d82fc JJ |
2374 | } |
2375 | ||
e9174d1e | 2376 | fn item_function(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, |
1a4d82fc | 2377 | f: &clean::Function) -> fmt::Result { |
cc61c64b XL |
2378 | let name_len = format!("{}{}{}{:#}fn {}{:#}", |
2379 | VisSpace(&it.visibility), | |
ea8adc8c | 2380 | ConstnessSpace(f.constness), |
cc61c64b XL |
2381 | UnsafetySpace(f.unsafety), |
2382 | AbiSpace(f.abi), | |
2383 | it.name.as_ref().unwrap(), | |
2384 | f.generics).len(); | |
ff7c6d11 | 2385 | write!(w, "{}<pre class='rust fn'>", render_spotlight_traits(it)?)?; |
8bb4bdeb | 2386 | render_attributes(w, it)?; |
ff7c6d11 XL |
2387 | write!(w, |
2388 | "{vis}{constness}{unsafety}{abi}fn {name}{generics}{decl}{where_clause}</pre>", | |
54a0048b | 2389 | vis = VisSpace(&it.visibility), |
ea8adc8c | 2390 | constness = ConstnessSpace(f.constness), |
1a4d82fc | 2391 | unsafety = UnsafetySpace(f.unsafety), |
9346a6ac | 2392 | abi = AbiSpace(f.abi), |
85aaf69f | 2393 | name = it.name.as_ref().unwrap(), |
1a4d82fc | 2394 | generics = f.generics, |
cc61c64b XL |
2395 | where_clause = WhereClause { gens: &f.generics, indent: 0, end_newline: true }, |
2396 | decl = Method { | |
ff7c6d11 XL |
2397 | decl: &f.decl, |
2398 | name_len, | |
2399 | indent: 0, | |
cc61c64b | 2400 | })?; |
e9174d1e | 2401 | document(w, cx, it) |
1a4d82fc JJ |
2402 | } |
2403 | ||
2404 | fn item_trait(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, | |
2405 | t: &clean::Trait) -> fmt::Result { | |
2406 | let mut bounds = String::new(); | |
476ff2be | 2407 | let mut bounds_plain = String::new(); |
9346a6ac AL |
2408 | if !t.bounds.is_empty() { |
2409 | if !bounds.is_empty() { | |
1a4d82fc | 2410 | bounds.push(' '); |
476ff2be | 2411 | bounds_plain.push(' '); |
1a4d82fc JJ |
2412 | } |
2413 | bounds.push_str(": "); | |
476ff2be | 2414 | bounds_plain.push_str(": "); |
1a4d82fc | 2415 | for (i, p) in t.bounds.iter().enumerate() { |
476ff2be SL |
2416 | if i > 0 { |
2417 | bounds.push_str(" + "); | |
2418 | bounds_plain.push_str(" + "); | |
2419 | } | |
85aaf69f | 2420 | bounds.push_str(&format!("{}", *p)); |
476ff2be | 2421 | bounds_plain.push_str(&format!("{:#}", *p)); |
1a4d82fc JJ |
2422 | } |
2423 | } | |
2424 | ||
2425 | // Output the trait definition | |
8bb4bdeb XL |
2426 | write!(w, "<pre class='rust trait'>")?; |
2427 | render_attributes(w, it)?; | |
2c00a5a8 | 2428 | write!(w, "{}{}{}trait {}{}{}", |
54a0048b SL |
2429 | VisSpace(&it.visibility), |
2430 | UnsafetySpace(t.unsafety), | |
2c00a5a8 | 2431 | if t.is_auto { "auto " } else { "" }, |
54a0048b SL |
2432 | it.name.as_ref().unwrap(), |
2433 | t.generics, | |
cc61c64b XL |
2434 | bounds)?; |
2435 | ||
2436 | if !t.generics.where_predicates.is_empty() { | |
2437 | write!(w, "{}", WhereClause { gens: &t.generics, indent: 0, end_newline: true })?; | |
2438 | } else { | |
2439 | write!(w, " ")?; | |
2440 | } | |
54a0048b SL |
2441 | |
2442 | let types = t.items.iter().filter(|m| m.is_associated_type()).collect::<Vec<_>>(); | |
2443 | let consts = t.items.iter().filter(|m| m.is_associated_const()).collect::<Vec<_>>(); | |
2444 | let required = t.items.iter().filter(|m| m.is_ty_method()).collect::<Vec<_>>(); | |
2445 | let provided = t.items.iter().filter(|m| m.is_method()).collect::<Vec<_>>(); | |
1a4d82fc | 2446 | |
9346a6ac | 2447 | if t.items.is_empty() { |
54a0048b | 2448 | write!(w, "{{ }}")?; |
1a4d82fc | 2449 | } else { |
a7813a04 | 2450 | // FIXME: we should be using a derived_id for the Anchors here |
54a0048b | 2451 | write!(w, "{{\n")?; |
85aaf69f | 2452 | for t in &types { |
54a0048b | 2453 | write!(w, " ")?; |
476ff2be | 2454 | render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait)?; |
54a0048b | 2455 | write!(w, ";\n")?; |
d9579d0f AL |
2456 | } |
2457 | if !types.is_empty() && !consts.is_empty() { | |
54a0048b | 2458 | w.write_str("\n")?; |
d9579d0f AL |
2459 | } |
2460 | for t in &consts { | |
54a0048b | 2461 | write!(w, " ")?; |
476ff2be | 2462 | render_assoc_item(w, t, AssocItemLink::Anchor(None), ItemType::Trait)?; |
54a0048b | 2463 | write!(w, ";\n")?; |
1a4d82fc | 2464 | } |
d9579d0f | 2465 | if !consts.is_empty() && !required.is_empty() { |
54a0048b | 2466 | w.write_str("\n")?; |
1a4d82fc | 2467 | } |
041b39d2 | 2468 | for (pos, m) in required.iter().enumerate() { |
54a0048b | 2469 | write!(w, " ")?; |
476ff2be | 2470 | render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait)?; |
54a0048b | 2471 | write!(w, ";\n")?; |
041b39d2 XL |
2472 | |
2473 | if pos < required.len() - 1 { | |
2474 | write!(w, "<div class='item-spacer'></div>")?; | |
2475 | } | |
1a4d82fc | 2476 | } |
9346a6ac | 2477 | if !required.is_empty() && !provided.is_empty() { |
54a0048b | 2478 | w.write_str("\n")?; |
1a4d82fc | 2479 | } |
041b39d2 | 2480 | for (pos, m) in provided.iter().enumerate() { |
54a0048b | 2481 | write!(w, " ")?; |
476ff2be | 2482 | render_assoc_item(w, m, AssocItemLink::Anchor(None), ItemType::Trait)?; |
cc61c64b XL |
2483 | match m.inner { |
2484 | clean::MethodItem(ref inner) if !inner.generics.where_predicates.is_empty() => { | |
2485 | write!(w, ",\n {{ ... }}\n")?; | |
2486 | }, | |
2487 | _ => { | |
2488 | write!(w, " {{ ... }}\n")?; | |
2489 | }, | |
2490 | } | |
041b39d2 XL |
2491 | if pos < provided.len() - 1 { |
2492 | write!(w, "<div class='item-spacer'></div>")?; | |
2493 | } | |
1a4d82fc | 2494 | } |
54a0048b | 2495 | write!(w, "}}")?; |
1a4d82fc | 2496 | } |
54a0048b | 2497 | write!(w, "</pre>")?; |
1a4d82fc JJ |
2498 | |
2499 | // Trait documentation | |
54a0048b | 2500 | document(w, cx, it)?; |
1a4d82fc | 2501 | |
7453a54e | 2502 | fn trait_item(w: &mut fmt::Formatter, cx: &Context, m: &clean::Item, t: &clean::Item) |
1a4d82fc | 2503 | -> fmt::Result { |
92a42be0 | 2504 | let name = m.name.as_ref().unwrap(); |
c30ab7b3 | 2505 | let item_type = m.type_(); |
9e0c209e SL |
2506 | let id = derive_id(format!("{}.{}", item_type, name)); |
2507 | let ns_id = derive_id(format!("{}.{}", name, item_type.name_space())); | |
ff7c6d11 | 2508 | write!(w, "{extra}<h3 id='{id}' class='method'>\ |
9e0c209e | 2509 | <span id='{ns_id}' class='invisible'><code>", |
ff7c6d11 | 2510 | extra = render_spotlight_traits(m)?, |
54a0048b | 2511 | id = id, |
9e0c209e | 2512 | ns_id = ns_id)?; |
476ff2be | 2513 | render_assoc_item(w, m, AssocItemLink::Anchor(Some(&id)), ItemType::Impl)?; |
54a0048b SL |
2514 | write!(w, "</code>")?; |
2515 | render_stability_since(w, m, t)?; | |
9e0c209e | 2516 | write!(w, "</span></h3>")?; |
54a0048b | 2517 | document(w, cx, m)?; |
1a4d82fc JJ |
2518 | Ok(()) |
2519 | } | |
2520 | ||
9346a6ac | 2521 | if !types.is_empty() { |
54a0048b | 2522 | write!(w, " |
3b2f2976 XL |
2523 | <h2 id='associated-types' class='small-section-header'> |
2524 | Associated Types<a href='#associated-types' class='anchor'></a> | |
2525 | </h2> | |
1a4d82fc | 2526 | <div class='methods'> |
54a0048b | 2527 | ")?; |
85aaf69f | 2528 | for t in &types { |
54a0048b | 2529 | trait_item(w, cx, *t, it)?; |
1a4d82fc | 2530 | } |
54a0048b | 2531 | write!(w, "</div>")?; |
1a4d82fc JJ |
2532 | } |
2533 | ||
d9579d0f | 2534 | if !consts.is_empty() { |
54a0048b | 2535 | write!(w, " |
3b2f2976 XL |
2536 | <h2 id='associated-const' class='small-section-header'> |
2537 | Associated Constants<a href='#associated-const' class='anchor'></a> | |
2538 | </h2> | |
d9579d0f | 2539 | <div class='methods'> |
54a0048b | 2540 | ")?; |
d9579d0f | 2541 | for t in &consts { |
54a0048b | 2542 | trait_item(w, cx, *t, it)?; |
d9579d0f | 2543 | } |
54a0048b | 2544 | write!(w, "</div>")?; |
d9579d0f AL |
2545 | } |
2546 | ||
1a4d82fc | 2547 | // Output the documentation for each function individually |
9346a6ac | 2548 | if !required.is_empty() { |
54a0048b | 2549 | write!(w, " |
3b2f2976 XL |
2550 | <h2 id='required-methods' class='small-section-header'> |
2551 | Required Methods<a href='#required-methods' class='anchor'></a> | |
2552 | </h2> | |
1a4d82fc | 2553 | <div class='methods'> |
54a0048b | 2554 | ")?; |
85aaf69f | 2555 | for m in &required { |
54a0048b | 2556 | trait_item(w, cx, *m, it)?; |
1a4d82fc | 2557 | } |
54a0048b | 2558 | write!(w, "</div>")?; |
1a4d82fc | 2559 | } |
9346a6ac | 2560 | if !provided.is_empty() { |
54a0048b | 2561 | write!(w, " |
3b2f2976 XL |
2562 | <h2 id='provided-methods' class='small-section-header'> |
2563 | Provided Methods<a href='#provided-methods' class='anchor'></a> | |
2564 | </h2> | |
1a4d82fc | 2565 | <div class='methods'> |
54a0048b | 2566 | ")?; |
85aaf69f | 2567 | for m in &provided { |
54a0048b | 2568 | trait_item(w, cx, *m, it)?; |
1a4d82fc | 2569 | } |
54a0048b | 2570 | write!(w, "</div>")?; |
1a4d82fc JJ |
2571 | } |
2572 | ||
9346a6ac | 2573 | // If there are methods directly on this trait object, render them here. |
54a0048b | 2574 | render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)?; |
9346a6ac | 2575 | |
1a4d82fc | 2576 | let cache = cache(); |
ea8adc8c | 2577 | let impl_header = " |
3b2f2976 XL |
2578 | <h2 id='implementors' class='small-section-header'> |
2579 | Implementors<a href='#implementors' class='anchor'></a> | |
2580 | </h2> | |
1a4d82fc | 2581 | <ul class='item-list' id='implementors-list'> |
ea8adc8c | 2582 | "; |
3157f602 | 2583 | if let Some(implementors) = cache.implementors.get(&it.def_id) { |
8bb4bdeb XL |
2584 | // The DefId is for the first Type found with that name. The bool is |
2585 | // if any Types with the same name but different DefId have been found. | |
2586 | let mut implementor_dups: FxHashMap<&str, (DefId, bool)> = FxHashMap(); | |
32a655c1 | 2587 | for implementor in implementors { |
2c00a5a8 | 2588 | match implementor.inner_impl().for_ { |
8bb4bdeb XL |
2589 | clean::ResolvedPath { ref path, did, is_generic: false, .. } | |
2590 | clean::BorrowedRef { | |
2591 | type_: box clean::ResolvedPath { ref path, did, is_generic: false, .. }, | |
2592 | .. | |
2593 | } => { | |
2594 | let &mut (prev_did, ref mut has_duplicates) = | |
2595 | implementor_dups.entry(path.last_name()).or_insert((did, false)); | |
2596 | if prev_did != did { | |
2597 | *has_duplicates = true; | |
2598 | } | |
2599 | } | |
2600 | _ => {} | |
32a655c1 SL |
2601 | } |
2602 | } | |
2603 | ||
ea8adc8c | 2604 | let (local, foreign) = implementors.iter() |
2c00a5a8 | 2605 | .partition::<Vec<_>, _>(|i| i.inner_impl().for_.def_id() |
ea8adc8c XL |
2606 | .map_or(true, |d| cache.paths.contains_key(&d))); |
2607 | ||
2608 | if !foreign.is_empty() { | |
2609 | write!(w, " | |
abe05a73 | 2610 | <h2 id='foreign-impls' class='small-section-header'> |
ea8adc8c XL |
2611 | Implementations on Foreign Types<a href='#foreign-impls' class='anchor'></a> |
2612 | </h2> | |
2613 | ")?; | |
2614 | ||
2615 | for implementor in foreign { | |
2c00a5a8 XL |
2616 | let assoc_link = AssocItemLink::GotoSource( |
2617 | implementor.impl_item.def_id, &implementor.inner_impl().provided_trait_methods | |
2618 | ); | |
2619 | render_impl(w, cx, &implementor, assoc_link, | |
2620 | RenderMode::Normal, implementor.impl_item.stable_since(), false)?; | |
ea8adc8c XL |
2621 | } |
2622 | } | |
2623 | ||
2624 | write!(w, "{}", impl_header)?; | |
2625 | ||
2626 | for implementor in local { | |
2627 | write!(w, "<li>")?; | |
2c00a5a8 XL |
2628 | if let Some(l) = (Item { cx, item: &implementor.impl_item }).src_href() { |
2629 | write!(w, "<div class='out-of-band'>")?; | |
2630 | write!(w, "<a class='srclink' href='{}' title='{}'>[src]</a>", | |
2631 | l, "goto source code")?; | |
2632 | write!(w, "</div>")?; | |
ea8adc8c XL |
2633 | } |
2634 | write!(w, "<code>")?; | |
32a655c1 SL |
2635 | // If there's already another implementor that has the same abbridged name, use the |
2636 | // full path, for example in `std::iter::ExactSizeIterator` | |
2c00a5a8 | 2637 | let use_absolute = match implementor.inner_impl().for_ { |
8bb4bdeb XL |
2638 | clean::ResolvedPath { ref path, is_generic: false, .. } | |
2639 | clean::BorrowedRef { | |
2640 | type_: box clean::ResolvedPath { ref path, is_generic: false, .. }, | |
2641 | .. | |
2642 | } => implementor_dups[path.last_name()].1, | |
2643 | _ => false, | |
32a655c1 | 2644 | }; |
2c00a5a8 XL |
2645 | fmt_impl_for_trait_page(&implementor.inner_impl(), w, use_absolute)?; |
2646 | for it in &implementor.inner_impl().items { | |
3b2f2976 XL |
2647 | if let clean::TypedefItem(ref tydef, _) = it.inner { |
2648 | write!(w, "<span class=\"where fmt-newline\"> ")?; | |
2649 | assoc_type(w, it, &vec![], Some(&tydef.type_), AssocItemLink::Anchor(None))?; | |
2650 | write!(w, ";</span>")?; | |
2651 | } | |
2652 | } | |
3157f602 | 2653 | writeln!(w, "</code></li>")?; |
1a4d82fc | 2654 | } |
ea8adc8c XL |
2655 | } else { |
2656 | // even without any implementations to write in, we still want the heading and list, so the | |
2657 | // implementors javascript file pulled in below has somewhere to write the impls into | |
2658 | write!(w, "{}", impl_header)?; | |
1a4d82fc | 2659 | } |
54a0048b SL |
2660 | write!(w, "</ul>")?; |
2661 | write!(w, r#"<script type="text/javascript" async | |
2662 | src="{root_path}/implementors/{path}/{ty}.{name}.js"> | |
2663 | </script>"#, | |
2664 | root_path = vec![".."; cx.current.len()].join("/"), | |
2665 | path = if it.def_id.is_local() { | |
2666 | cx.current.join("/") | |
2667 | } else { | |
5bcae85e | 2668 | let (ref path, _) = cache.external_paths[&it.def_id]; |
54a0048b SL |
2669 | path[..path.len() - 1].join("/") |
2670 | }, | |
c30ab7b3 | 2671 | ty = it.type_().css_class(), |
54a0048b | 2672 | name = *it.name.as_ref().unwrap())?; |
1a4d82fc JJ |
2673 | Ok(()) |
2674 | } | |
2675 | ||
54a0048b SL |
2676 | fn naive_assoc_href(it: &clean::Item, link: AssocItemLink) -> String { |
2677 | use html::item_type::ItemType::*; | |
2678 | ||
2679 | let name = it.name.as_ref().unwrap(); | |
c30ab7b3 | 2680 | let ty = match it.type_() { |
54a0048b SL |
2681 | Typedef | AssociatedType => AssociatedType, |
2682 | s@_ => s, | |
2683 | }; | |
2684 | ||
2685 | let anchor = format!("#{}.{}", ty, name); | |
2686 | match link { | |
a7813a04 XL |
2687 | AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id), |
2688 | AssocItemLink::Anchor(None) => anchor, | |
54a0048b SL |
2689 | AssocItemLink::GotoSource(did, _) => { |
2690 | href(did).map(|p| format!("{}{}", p.0, anchor)).unwrap_or(anchor) | |
2691 | } | |
2692 | } | |
2693 | } | |
2694 | ||
2695 | fn assoc_const(w: &mut fmt::Formatter, | |
2696 | it: &clean::Item, | |
2697 | ty: &clean::Type, | |
8bb4bdeb | 2698 | _default: Option<&String>, |
54a0048b | 2699 | link: AssocItemLink) -> fmt::Result { |
ff7c6d11 XL |
2700 | write!(w, "{}const <a href='{}' class=\"constant\"><b>{}</b></a>: {}", |
2701 | VisSpace(&it.visibility), | |
54a0048b | 2702 | naive_assoc_href(it, link), |
8bb4bdeb XL |
2703 | it.name.as_ref().unwrap(), |
2704 | ty)?; | |
d9579d0f AL |
2705 | Ok(()) |
2706 | } | |
2707 | ||
ff7c6d11 XL |
2708 | fn assoc_type<W: fmt::Write>(w: &mut W, it: &clean::Item, |
2709 | bounds: &Vec<clean::TyParamBound>, | |
2710 | default: Option<&clean::Type>, | |
2711 | link: AssocItemLink) -> fmt::Result { | |
8bb4bdeb | 2712 | write!(w, "type <a href='{}' class=\"type\">{}</a>", |
54a0048b SL |
2713 | naive_assoc_href(it, link), |
2714 | it.name.as_ref().unwrap())?; | |
9346a6ac | 2715 | if !bounds.is_empty() { |
54a0048b | 2716 | write!(w, ": {}", TyParamBounds(bounds))? |
1a4d82fc | 2717 | } |
54a0048b SL |
2718 | if let Some(default) = default { |
2719 | write!(w, " = {}", default)?; | |
1a4d82fc JJ |
2720 | } |
2721 | Ok(()) | |
2722 | } | |
2723 | ||
7453a54e SL |
2724 | fn render_stability_since_raw<'a>(w: &mut fmt::Formatter, |
2725 | ver: Option<&'a str>, | |
2726 | containing_ver: Option<&'a str>) -> fmt::Result { | |
54a0048b SL |
2727 | if let Some(v) = ver { |
2728 | if containing_ver != ver && v.len() > 0 { | |
a7813a04 | 2729 | write!(w, "<div class='since' title='Stable since Rust version {0}'>{0}</div>", |
54a0048b | 2730 | v)? |
7453a54e SL |
2731 | } |
2732 | } | |
7453a54e SL |
2733 | Ok(()) |
2734 | } | |
2735 | ||
2736 | fn render_stability_since(w: &mut fmt::Formatter, | |
2737 | item: &clean::Item, | |
2738 | containing_item: &clean::Item) -> fmt::Result { | |
2739 | render_stability_since_raw(w, item.stable_since(), containing_item.stable_since()) | |
2740 | } | |
2741 | ||
54a0048b SL |
2742 | fn render_assoc_item(w: &mut fmt::Formatter, |
2743 | item: &clean::Item, | |
476ff2be SL |
2744 | link: AssocItemLink, |
2745 | parent: ItemType) -> fmt::Result { | |
62682a34 | 2746 | fn method(w: &mut fmt::Formatter, |
54a0048b | 2747 | meth: &clean::Item, |
e9174d1e SL |
2748 | unsafety: hir::Unsafety, |
2749 | constness: hir::Constness, | |
62682a34 SL |
2750 | abi: abi::Abi, |
2751 | g: &clean::Generics, | |
62682a34 | 2752 | d: &clean::FnDecl, |
476ff2be SL |
2753 | link: AssocItemLink, |
2754 | parent: ItemType) | |
62682a34 | 2755 | -> fmt::Result { |
54a0048b | 2756 | let name = meth.name.as_ref().unwrap(); |
c30ab7b3 | 2757 | let anchor = format!("#{}.{}", meth.type_(), name); |
9346a6ac | 2758 | let href = match link { |
a7813a04 XL |
2759 | AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id), |
2760 | AssocItemLink::Anchor(None) => anchor, | |
54a0048b SL |
2761 | AssocItemLink::GotoSource(did, provided_methods) => { |
2762 | // We're creating a link from an impl-item to the corresponding | |
2763 | // trait-item and need to map the anchored type accordingly. | |
2764 | let ty = if provided_methods.contains(name) { | |
2765 | ItemType::Method | |
2766 | } else { | |
2767 | ItemType::TyMethod | |
2768 | }; | |
2769 | ||
2770 | href(did).map(|p| format!("{}#{}.{}", p.0, ty, name)).unwrap_or(anchor) | |
9346a6ac AL |
2771 | } |
2772 | }; | |
ea8adc8c XL |
2773 | let mut head_len = format!("{}{}{}{:#}fn {}{:#}", |
2774 | VisSpace(&meth.visibility), | |
2775 | ConstnessSpace(constness), | |
cc61c64b XL |
2776 | UnsafetySpace(unsafety), |
2777 | AbiSpace(abi), | |
2778 | name, | |
2779 | *g).len(); | |
2780 | let (indent, end_newline) = if parent == ItemType::Trait { | |
2781 | head_len += 4; | |
2782 | (4, false) | |
476ff2be | 2783 | } else { |
cc61c64b | 2784 | (0, true) |
476ff2be | 2785 | }; |
ea8adc8c | 2786 | write!(w, "{}{}{}{}fn <a href='{href}' class='fnname'>{name}</a>\ |
1a4d82fc | 2787 | {generics}{decl}{where_clause}", |
ea8adc8c XL |
2788 | VisSpace(&meth.visibility), |
2789 | ConstnessSpace(constness), | |
92a42be0 | 2790 | UnsafetySpace(unsafety), |
a7813a04 | 2791 | AbiSpace(abi), |
9346a6ac AL |
2792 | href = href, |
2793 | name = name, | |
1a4d82fc | 2794 | generics = *g, |
cc61c64b XL |
2795 | decl = Method { |
2796 | decl: d, | |
2797 | name_len: head_len, | |
3b2f2976 | 2798 | indent, |
cc61c64b XL |
2799 | }, |
2800 | where_clause = WhereClause { | |
2801 | gens: g, | |
3b2f2976 XL |
2802 | indent, |
2803 | end_newline, | |
cc61c64b | 2804 | }) |
1a4d82fc | 2805 | } |
54a0048b SL |
2806 | match item.inner { |
2807 | clean::StrippedItem(..) => Ok(()), | |
1a4d82fc | 2808 | clean::TyMethodItem(ref m) => { |
54a0048b | 2809 | method(w, item, m.unsafety, hir::Constness::NotConst, |
476ff2be | 2810 | m.abi, &m.generics, &m.decl, link, parent) |
1a4d82fc JJ |
2811 | } |
2812 | clean::MethodItem(ref m) => { | |
54a0048b | 2813 | method(w, item, m.unsafety, m.constness, |
476ff2be | 2814 | m.abi, &m.generics, &m.decl, link, parent) |
1a4d82fc | 2815 | } |
d9579d0f | 2816 | clean::AssociatedConstItem(ref ty, ref default) => { |
54a0048b | 2817 | assoc_const(w, item, ty, default.as_ref(), link) |
d9579d0f | 2818 | } |
c34b1796 | 2819 | clean::AssociatedTypeItem(ref bounds, ref default) => { |
54a0048b | 2820 | assoc_type(w, item, bounds, default.as_ref(), link) |
1a4d82fc | 2821 | } |
d9579d0f | 2822 | _ => panic!("render_assoc_item called on non-associated-item") |
1a4d82fc JJ |
2823 | } |
2824 | } | |
2825 | ||
e9174d1e | 2826 | fn item_struct(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, |
1a4d82fc | 2827 | s: &clean::Struct) -> fmt::Result { |
54a0048b SL |
2828 | write!(w, "<pre class='rust struct'>")?; |
2829 | render_attributes(w, it)?; | |
2830 | render_struct(w, | |
2831 | it, | |
2832 | Some(&s.generics), | |
2833 | s.struct_type, | |
2834 | &s.fields, | |
2835 | "", | |
2836 | true)?; | |
2837 | write!(w, "</pre>")?; | |
54a0048b SL |
2838 | |
2839 | document(w, cx, it)?; | |
3157f602 | 2840 | let mut fields = s.fields.iter().filter_map(|f| { |
1a4d82fc | 2841 | match f.inner { |
3157f602 XL |
2842 | clean::StructFieldItem(ref ty) => Some((f, ty)), |
2843 | _ => None, | |
1a4d82fc JJ |
2844 | } |
2845 | }).peekable(); | |
2846 | if let doctree::Plain = s.struct_type { | |
2847 | if fields.peek().is_some() { | |
3b2f2976 XL |
2848 | write!(w, "<h2 id='fields' class='fields small-section-header'> |
2849 | Fields<a href='#fields' class='anchor'></a></h2>")?; | |
3157f602 | 2850 | for (field, ty) in fields { |
9e0c209e SL |
2851 | let id = derive_id(format!("{}.{}", |
2852 | ItemType::StructField, | |
2853 | field.name.as_ref().unwrap())); | |
2854 | let ns_id = derive_id(format!("{}.{}", | |
2855 | field.name.as_ref().unwrap(), | |
2856 | ItemType::StructField.name_space())); | |
ea8adc8c XL |
2857 | write!(w, "<span id=\"{id}\" class=\"{item_type} small-section-header\"> |
2858 | <a href=\"#{id}\" class=\"anchor field\"></a> | |
2859 | <span id=\"{ns_id}\" class='invisible'> | |
9e0c209e | 2860 | <code>{name}: {ty}</code> |
8bb4bdeb | 2861 | </span></span>", |
9e0c209e SL |
2862 | item_type = ItemType::StructField, |
2863 | id = id, | |
2864 | ns_id = ns_id, | |
3157f602 XL |
2865 | name = field.name.as_ref().unwrap(), |
2866 | ty = ty)?; | |
8bb4bdeb XL |
2867 | if let Some(stability_class) = field.stability_class() { |
2868 | write!(w, "<span class='stab {stab}'></span>", | |
2869 | stab = stability_class)?; | |
2870 | } | |
54a0048b | 2871 | document(w, cx, field)?; |
1a4d82fc | 2872 | } |
1a4d82fc JJ |
2873 | } |
2874 | } | |
7453a54e | 2875 | render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) |
1a4d82fc JJ |
2876 | } |
2877 | ||
9e0c209e SL |
2878 | fn item_union(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, |
2879 | s: &clean::Union) -> fmt::Result { | |
2880 | write!(w, "<pre class='rust union'>")?; | |
2881 | render_attributes(w, it)?; | |
2882 | render_union(w, | |
2883 | it, | |
2884 | Some(&s.generics), | |
2885 | &s.fields, | |
2886 | "", | |
2887 | true)?; | |
2888 | write!(w, "</pre>")?; | |
2889 | ||
2890 | document(w, cx, it)?; | |
2891 | let mut fields = s.fields.iter().filter_map(|f| { | |
2892 | match f.inner { | |
2893 | clean::StructFieldItem(ref ty) => Some((f, ty)), | |
2894 | _ => None, | |
2895 | } | |
2896 | }).peekable(); | |
2897 | if fields.peek().is_some() { | |
3b2f2976 XL |
2898 | write!(w, "<h2 id='fields' class='fields small-section-header'> |
2899 | Fields<a href='#fields' class='anchor'></a></h2>")?; | |
9e0c209e | 2900 | for (field, ty) in fields { |
8bb4bdeb XL |
2901 | write!(w, "<span id='{shortty}.{name}' class=\"{shortty}\"><code>{name}: {ty}</code> |
2902 | </span>", | |
9e0c209e | 2903 | shortty = ItemType::StructField, |
9e0c209e SL |
2904 | name = field.name.as_ref().unwrap(), |
2905 | ty = ty)?; | |
8bb4bdeb XL |
2906 | if let Some(stability_class) = field.stability_class() { |
2907 | write!(w, "<span class='stab {stab}'></span>", | |
2908 | stab = stability_class)?; | |
2909 | } | |
9e0c209e SL |
2910 | document(w, cx, field)?; |
2911 | } | |
2912 | } | |
2913 | render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) | |
2914 | } | |
2915 | ||
e9174d1e | 2916 | fn item_enum(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, |
1a4d82fc | 2917 | e: &clean::Enum) -> fmt::Result { |
54a0048b SL |
2918 | write!(w, "<pre class='rust enum'>")?; |
2919 | render_attributes(w, it)?; | |
2920 | write!(w, "{}enum {}{}{}", | |
2921 | VisSpace(&it.visibility), | |
2922 | it.name.as_ref().unwrap(), | |
2923 | e.generics, | |
cc61c64b | 2924 | WhereClause { gens: &e.generics, indent: 0, end_newline: true })?; |
9346a6ac | 2925 | if e.variants.is_empty() && !e.variants_stripped { |
54a0048b | 2926 | write!(w, " {{}}")?; |
1a4d82fc | 2927 | } else { |
54a0048b | 2928 | write!(w, " {{\n")?; |
85aaf69f | 2929 | for v in &e.variants { |
54a0048b | 2930 | write!(w, " ")?; |
85aaf69f | 2931 | let name = v.name.as_ref().unwrap(); |
1a4d82fc JJ |
2932 | match v.inner { |
2933 | clean::VariantItem(ref var) => { | |
2934 | match var.kind { | |
c30ab7b3 SL |
2935 | clean::VariantKind::CLike => write!(w, "{}", name)?, |
2936 | clean::VariantKind::Tuple(ref tys) => { | |
54a0048b | 2937 | write!(w, "{}(", name)?; |
1a4d82fc JJ |
2938 | for (i, ty) in tys.iter().enumerate() { |
2939 | if i > 0 { | |
3157f602 | 2940 | write!(w, ", ")? |
1a4d82fc | 2941 | } |
54a0048b | 2942 | write!(w, "{}", *ty)?; |
1a4d82fc | 2943 | } |
54a0048b | 2944 | write!(w, ")")?; |
1a4d82fc | 2945 | } |
c30ab7b3 | 2946 | clean::VariantKind::Struct(ref s) => { |
54a0048b SL |
2947 | render_struct(w, |
2948 | v, | |
2949 | None, | |
2950 | s.struct_type, | |
2951 | &s.fields, | |
2952 | " ", | |
2953 | false)?; | |
1a4d82fc JJ |
2954 | } |
2955 | } | |
2956 | } | |
2957 | _ => unreachable!() | |
2958 | } | |
54a0048b | 2959 | write!(w, ",\n")?; |
1a4d82fc JJ |
2960 | } |
2961 | ||
2962 | if e.variants_stripped { | |
54a0048b | 2963 | write!(w, " // some variants omitted\n")?; |
1a4d82fc | 2964 | } |
54a0048b | 2965 | write!(w, "}}")?; |
1a4d82fc | 2966 | } |
54a0048b | 2967 | write!(w, "</pre>")?; |
1a4d82fc | 2968 | |
54a0048b | 2969 | document(w, cx, it)?; |
9346a6ac | 2970 | if !e.variants.is_empty() { |
3b2f2976 XL |
2971 | write!(w, "<h2 id='variants' class='variants small-section-header'> |
2972 | Variants<a href='#variants' class='anchor'></a></h2>\n")?; | |
85aaf69f | 2973 | for variant in &e.variants { |
9e0c209e SL |
2974 | let id = derive_id(format!("{}.{}", |
2975 | ItemType::Variant, | |
2976 | variant.name.as_ref().unwrap())); | |
2977 | let ns_id = derive_id(format!("{}.{}", | |
2978 | variant.name.as_ref().unwrap(), | |
2979 | ItemType::Variant.name_space())); | |
ea8adc8c XL |
2980 | write!(w, "<span id=\"{id}\" class=\"variant small-section-header\">\ |
2981 | <a href=\"#{id}\" class=\"anchor field\"></a>\ | |
9e0c209e SL |
2982 | <span id='{ns_id}' class='invisible'><code>{name}", |
2983 | id = id, | |
2984 | ns_id = ns_id, | |
54a0048b | 2985 | name = variant.name.as_ref().unwrap())?; |
3157f602 | 2986 | if let clean::VariantItem(ref var) = variant.inner { |
c30ab7b3 | 2987 | if let clean::VariantKind::Tuple(ref tys) = var.kind { |
3157f602 XL |
2988 | write!(w, "(")?; |
2989 | for (i, ty) in tys.iter().enumerate() { | |
2990 | if i > 0 { | |
2991 | write!(w, ", ")?; | |
2992 | } | |
2993 | write!(w, "{}", *ty)?; | |
2994 | } | |
2995 | write!(w, ")")?; | |
2996 | } | |
2997 | } | |
9e0c209e | 2998 | write!(w, "</code></span></span>")?; |
54a0048b SL |
2999 | document(w, cx, variant)?; |
3000 | ||
c30ab7b3 SL |
3001 | use clean::{Variant, VariantKind}; |
3002 | if let clean::VariantItem(Variant { | |
3003 | kind: VariantKind::Struct(ref s) | |
3004 | }) = variant.inner { | |
476ff2be SL |
3005 | let variant_id = derive_id(format!("{}.{}.fields", |
3006 | ItemType::Variant, | |
3007 | variant.name.as_ref().unwrap())); | |
3008 | write!(w, "<span class='docblock autohide sub-variant' id='{id}'>", | |
3009 | id = variant_id)?; | |
3010 | write!(w, "<h3 class='fields'>Fields of <code>{name}</code></h3>\n | |
3011 | <table>", name = variant.name.as_ref().unwrap())?; | |
3157f602 XL |
3012 | for field in &s.fields { |
3013 | use clean::StructFieldItem; | |
3014 | if let StructFieldItem(ref ty) = field.inner { | |
9e0c209e SL |
3015 | let id = derive_id(format!("variant.{}.field.{}", |
3016 | variant.name.as_ref().unwrap(), | |
3017 | field.name.as_ref().unwrap())); | |
3018 | let ns_id = derive_id(format!("{}.{}.{}.{}", | |
3019 | variant.name.as_ref().unwrap(), | |
3020 | ItemType::Variant.name_space(), | |
3021 | field.name.as_ref().unwrap(), | |
3022 | ItemType::StructField.name_space())); | |
3157f602 | 3023 | write!(w, "<tr><td \ |
9e0c209e SL |
3024 | id='{id}'>\ |
3025 | <span id='{ns_id}' class='invisible'>\ | |
3026 | <code>{f}: {t}</code></span></td><td>", | |
3027 | id = id, | |
3028 | ns_id = ns_id, | |
3157f602 XL |
3029 | f = field.name.as_ref().unwrap(), |
3030 | t = *ty)?; | |
3031 | document(w, cx, field)?; | |
3032 | write!(w, "</td></tr>")?; | |
3033 | } | |
1a4d82fc | 3034 | } |
476ff2be | 3035 | write!(w, "</table></span>")?; |
1a4d82fc | 3036 | } |
54a0048b | 3037 | render_stability_since(w, variant, it)?; |
1a4d82fc | 3038 | } |
1a4d82fc | 3039 | } |
54a0048b | 3040 | render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All)?; |
1a4d82fc JJ |
3041 | Ok(()) |
3042 | } | |
3043 | ||
476ff2be SL |
3044 | fn render_attribute(attr: &ast::MetaItem) -> Option<String> { |
3045 | let name = attr.name(); | |
3046 | ||
3047 | if attr.is_word() { | |
3048 | Some(format!("{}", name)) | |
3049 | } else if let Some(v) = attr.value_str() { | |
cc61c64b | 3050 | Some(format!("{} = {:?}", name, v.as_str())) |
476ff2be SL |
3051 | } else if let Some(values) = attr.meta_item_list() { |
3052 | let display: Vec<_> = values.iter().filter_map(|attr| { | |
3053 | attr.meta_item().and_then(|mi| render_attribute(mi)) | |
3054 | }).collect(); | |
3055 | ||
3056 | if display.len() > 0 { | |
3057 | Some(format!("{}({})", name, display.join(", "))) | |
3058 | } else { | |
3059 | None | |
3060 | } | |
3061 | } else { | |
3062 | None | |
3063 | } | |
3064 | } | |
3065 | ||
3066 | const ATTRIBUTE_WHITELIST: &'static [&'static str] = &[ | |
3067 | "export_name", | |
3068 | "lang", | |
3069 | "link_section", | |
3070 | "must_use", | |
3071 | "no_mangle", | |
3072 | "repr", | |
3073 | "unsafe_destructor_blind_to_params" | |
3074 | ]; | |
3075 | ||
85aaf69f | 3076 | fn render_attributes(w: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result { |
476ff2be SL |
3077 | let mut attrs = String::new(); |
3078 | ||
3079 | for attr in &it.attrs.other_attrs { | |
cc61c64b XL |
3080 | let name = attr.name().unwrap(); |
3081 | if !ATTRIBUTE_WHITELIST.contains(&&*name.as_str()) { | |
476ff2be | 3082 | continue; |
85aaf69f | 3083 | } |
cc61c64b | 3084 | if let Some(s) = render_attribute(&attr.meta().unwrap()) { |
476ff2be SL |
3085 | attrs.push_str(&format!("#[{}]\n", s)); |
3086 | } | |
3087 | } | |
3088 | if attrs.len() > 0 { | |
3089 | write!(w, "<div class=\"docblock attributes\">{}</div>", &attrs)?; | |
85aaf69f SL |
3090 | } |
3091 | Ok(()) | |
3092 | } | |
3093 | ||
1a4d82fc JJ |
3094 | fn render_struct(w: &mut fmt::Formatter, it: &clean::Item, |
3095 | g: Option<&clean::Generics>, | |
3096 | ty: doctree::StructType, | |
3097 | fields: &[clean::Item], | |
3098 | tab: &str, | |
3099 | structhead: bool) -> fmt::Result { | |
54a0048b SL |
3100 | write!(w, "{}{}{}", |
3101 | VisSpace(&it.visibility), | |
3102 | if structhead {"struct "} else {""}, | |
3103 | it.name.as_ref().unwrap())?; | |
3104 | if let Some(g) = g { | |
5bcae85e | 3105 | write!(w, "{}", g)? |
1a4d82fc JJ |
3106 | } |
3107 | match ty { | |
3108 | doctree::Plain => { | |
5bcae85e | 3109 | if let Some(g) = g { |
cc61c64b | 3110 | write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true })? |
5bcae85e | 3111 | } |
9e0c209e SL |
3112 | let mut has_visible_fields = false; |
3113 | write!(w, " {{")?; | |
85aaf69f | 3114 | for field in fields { |
54a0048b | 3115 | if let clean::StructFieldItem(ref ty) = field.inner { |
9e0c209e SL |
3116 | write!(w, "\n{} {}{}: {},", |
3117 | tab, | |
54a0048b SL |
3118 | VisSpace(&field.visibility), |
3119 | field.name.as_ref().unwrap(), | |
9e0c209e SL |
3120 | *ty)?; |
3121 | has_visible_fields = true; | |
54a0048b | 3122 | } |
1a4d82fc JJ |
3123 | } |
3124 | ||
9e0c209e SL |
3125 | if has_visible_fields { |
3126 | if it.has_stripped_fields().unwrap() { | |
3127 | write!(w, "\n{} // some fields omitted", tab)?; | |
3128 | } | |
3129 | write!(w, "\n{}", tab)?; | |
3130 | } else if it.has_stripped_fields().unwrap() { | |
3131 | // If there are no visible fields we can just display | |
3132 | // `{ /* fields omitted */ }` to save space. | |
3133 | write!(w, " /* fields omitted */ ")?; | |
1a4d82fc | 3134 | } |
54a0048b | 3135 | write!(w, "}}")?; |
1a4d82fc | 3136 | } |
9e0c209e | 3137 | doctree::Tuple => { |
54a0048b | 3138 | write!(w, "(")?; |
1a4d82fc JJ |
3139 | for (i, field) in fields.iter().enumerate() { |
3140 | if i > 0 { | |
54a0048b | 3141 | write!(w, ", ")?; |
1a4d82fc JJ |
3142 | } |
3143 | match field.inner { | |
54a0048b SL |
3144 | clean::StrippedItem(box clean::StructFieldItem(..)) => { |
3145 | write!(w, "_")? | |
1a4d82fc | 3146 | } |
54a0048b SL |
3147 | clean::StructFieldItem(ref ty) => { |
3148 | write!(w, "{}{}", VisSpace(&field.visibility), *ty)? | |
1a4d82fc JJ |
3149 | } |
3150 | _ => unreachable!() | |
3151 | } | |
3152 | } | |
5bcae85e SL |
3153 | write!(w, ")")?; |
3154 | if let Some(g) = g { | |
cc61c64b | 3155 | write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: false })? |
5bcae85e SL |
3156 | } |
3157 | write!(w, ";")?; | |
1a4d82fc JJ |
3158 | } |
3159 | doctree::Unit => { | |
5bcae85e SL |
3160 | // Needed for PhantomData. |
3161 | if let Some(g) = g { | |
cc61c64b | 3162 | write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: false })? |
5bcae85e | 3163 | } |
54a0048b | 3164 | write!(w, ";")?; |
1a4d82fc JJ |
3165 | } |
3166 | } | |
3167 | Ok(()) | |
3168 | } | |
3169 | ||
9e0c209e SL |
3170 | fn render_union(w: &mut fmt::Formatter, it: &clean::Item, |
3171 | g: Option<&clean::Generics>, | |
3172 | fields: &[clean::Item], | |
3173 | tab: &str, | |
3174 | structhead: bool) -> fmt::Result { | |
3175 | write!(w, "{}{}{}", | |
3176 | VisSpace(&it.visibility), | |
3177 | if structhead {"union "} else {""}, | |
3178 | it.name.as_ref().unwrap())?; | |
3179 | if let Some(g) = g { | |
3180 | write!(w, "{}", g)?; | |
cc61c64b | 3181 | write!(w, "{}", WhereClause { gens: g, indent: 0, end_newline: true })?; |
9e0c209e SL |
3182 | } |
3183 | ||
3184 | write!(w, " {{\n{}", tab)?; | |
3185 | for field in fields { | |
3186 | if let clean::StructFieldItem(ref ty) = field.inner { | |
3187 | write!(w, " {}{}: {},\n{}", | |
3188 | VisSpace(&field.visibility), | |
3189 | field.name.as_ref().unwrap(), | |
3190 | *ty, | |
3191 | tab)?; | |
3192 | } | |
3193 | } | |
3194 | ||
3195 | if it.has_stripped_fields().unwrap() { | |
3196 | write!(w, " // some fields omitted\n{}", tab)?; | |
3197 | } | |
3198 | write!(w, "}}")?; | |
3199 | Ok(()) | |
3200 | } | |
3201 | ||
9346a6ac | 3202 | #[derive(Copy, Clone)] |
54a0048b | 3203 | enum AssocItemLink<'a> { |
a7813a04 | 3204 | Anchor(Option<&'a str>), |
476ff2be | 3205 | GotoSource(DefId, &'a FxHashSet<String>), |
9346a6ac AL |
3206 | } |
3207 | ||
a7813a04 XL |
3208 | impl<'a> AssocItemLink<'a> { |
3209 | fn anchor(&self, id: &'a String) -> Self { | |
3210 | match *self { | |
3211 | AssocItemLink::Anchor(_) => { AssocItemLink::Anchor(Some(&id)) }, | |
3212 | ref other => *other, | |
3213 | } | |
3214 | } | |
3215 | } | |
3216 | ||
d9579d0f AL |
3217 | enum AssocItemRender<'a> { |
3218 | All, | |
9e0c209e SL |
3219 | DerefFor { trait_: &'a clean::Type, type_: &'a clean::Type, deref_mut_: bool } |
3220 | } | |
3221 | ||
3222 | #[derive(Copy, Clone, PartialEq)] | |
3223 | enum RenderMode { | |
3224 | Normal, | |
3225 | ForDeref { mut_: bool }, | |
d9579d0f AL |
3226 | } |
3227 | ||
3228 | fn render_assoc_items(w: &mut fmt::Formatter, | |
e9174d1e | 3229 | cx: &Context, |
7453a54e | 3230 | containing_item: &clean::Item, |
e9174d1e | 3231 | it: DefId, |
d9579d0f AL |
3232 | what: AssocItemRender) -> fmt::Result { |
3233 | let c = cache(); | |
3234 | let v = match c.impls.get(&it) { | |
3235 | Some(v) => v, | |
9346a6ac AL |
3236 | None => return Ok(()), |
3237 | }; | |
d9579d0f | 3238 | let (non_trait, traits): (Vec<_>, _) = v.iter().partition(|i| { |
a7813a04 | 3239 | i.inner_impl().trait_.is_none() |
d9579d0f | 3240 | }); |
9346a6ac | 3241 | if !non_trait.is_empty() { |
9e0c209e | 3242 | let render_mode = match what { |
d9579d0f | 3243 | AssocItemRender::All => { |
3b2f2976 XL |
3244 | write!(w, " |
3245 | <h2 id='methods' class='small-section-header'> | |
3246 | Methods<a href='#methods' class='anchor'></a> | |
3247 | </h2> | |
3248 | ")?; | |
9e0c209e | 3249 | RenderMode::Normal |
d9579d0f | 3250 | } |
9e0c209e | 3251 | AssocItemRender::DerefFor { trait_, type_, deref_mut_ } => { |
3b2f2976 XL |
3252 | write!(w, " |
3253 | <h2 id='deref-methods' class='small-section-header'> | |
3254 | Methods from {}<Target = {}><a href='#deref-methods' class='anchor'></a> | |
3255 | </h2> | |
3256 | ", trait_, type_)?; | |
9e0c209e | 3257 | RenderMode::ForDeref { mut_: deref_mut_ } |
d9579d0f AL |
3258 | } |
3259 | }; | |
9346a6ac | 3260 | for i in &non_trait { |
9e0c209e | 3261 | render_impl(w, cx, i, AssocItemLink::Anchor(None), render_mode, |
ea8adc8c | 3262 | containing_item.stable_since(), true)?; |
9346a6ac AL |
3263 | } |
3264 | } | |
d9579d0f | 3265 | if let AssocItemRender::DerefFor { .. } = what { |
54a0048b | 3266 | return Ok(()); |
d9579d0f | 3267 | } |
9346a6ac | 3268 | if !traits.is_empty() { |
d9579d0f | 3269 | let deref_impl = traits.iter().find(|t| { |
a7813a04 | 3270 | t.inner_impl().trait_.def_id() == c.deref_trait_did |
d9579d0f AL |
3271 | }); |
3272 | if let Some(impl_) = deref_impl { | |
9e0c209e SL |
3273 | let has_deref_mut = traits.iter().find(|t| { |
3274 | t.inner_impl().trait_.def_id() == c.deref_mut_trait_did | |
3275 | }).is_some(); | |
3276 | render_deref_methods(w, cx, impl_, containing_item, has_deref_mut)?; | |
d9579d0f | 3277 | } |
3b2f2976 XL |
3278 | write!(w, " |
3279 | <h2 id='implementations' class='small-section-header'> | |
3280 | Trait Implementations<a href='#implementations' class='anchor'></a> | |
3281 | </h2> | |
3282 | ")?; | |
3157f602 | 3283 | for i in &traits { |
9346a6ac | 3284 | let did = i.trait_did().unwrap(); |
a7813a04 | 3285 | let assoc_link = AssocItemLink::GotoSource(did, &i.inner_impl().provided_trait_methods); |
9e0c209e | 3286 | render_impl(w, cx, i, assoc_link, |
ea8adc8c | 3287 | RenderMode::Normal, containing_item.stable_since(), true)?; |
9346a6ac | 3288 | } |
1a4d82fc JJ |
3289 | } |
3290 | Ok(()) | |
3291 | } | |
3292 | ||
7453a54e | 3293 | fn render_deref_methods(w: &mut fmt::Formatter, cx: &Context, impl_: &Impl, |
9e0c209e | 3294 | container_item: &clean::Item, deref_mut: bool) -> fmt::Result { |
a7813a04 XL |
3295 | let deref_type = impl_.inner_impl().trait_.as_ref().unwrap(); |
3296 | let target = impl_.inner_impl().items.iter().filter_map(|item| { | |
d9579d0f | 3297 | match item.inner { |
62682a34 | 3298 | clean::TypedefItem(ref t, true) => Some(&t.type_), |
d9579d0f AL |
3299 | _ => None, |
3300 | } | |
62682a34 | 3301 | }).next().expect("Expected associated type binding"); |
9e0c209e SL |
3302 | let what = AssocItemRender::DerefFor { trait_: deref_type, type_: target, |
3303 | deref_mut_: deref_mut }; | |
54a0048b SL |
3304 | if let Some(did) = target.def_id() { |
3305 | render_assoc_items(w, cx, container_item, did, what) | |
3306 | } else { | |
3307 | if let Some(prim) = target.primitive_type() { | |
476ff2be | 3308 | if let Some(&did) = cache().primitive_locations.get(&prim) { |
54a0048b | 3309 | render_assoc_items(w, cx, container_item, did, what)?; |
d9579d0f | 3310 | } |
d9579d0f | 3311 | } |
54a0048b | 3312 | Ok(()) |
1a4d82fc | 3313 | } |
d9579d0f AL |
3314 | } |
3315 | ||
abe05a73 XL |
3316 | fn should_render_item(item: &clean::Item, deref_mut_: bool) -> bool { |
3317 | let self_type_opt = match item.inner { | |
3318 | clean::MethodItem(ref method) => method.decl.self_type(), | |
3319 | clean::TyMethodItem(ref method) => method.decl.self_type(), | |
3320 | _ => None | |
3321 | }; | |
3322 | ||
3323 | if let Some(self_ty) = self_type_opt { | |
3324 | let (by_mut_ref, by_box, by_value) = match self_ty { | |
3325 | SelfTy::SelfBorrowed(_, mutability) | | |
3326 | SelfTy::SelfExplicit(clean::BorrowedRef { mutability, .. }) => { | |
3327 | (mutability == Mutability::Mutable, false, false) | |
3328 | }, | |
3329 | SelfTy::SelfExplicit(clean::ResolvedPath { did, .. }) => { | |
3330 | (false, Some(did) == cache().owned_box_did, false) | |
3331 | }, | |
3332 | SelfTy::SelfValue => (false, false, true), | |
3333 | _ => (false, false, false), | |
3334 | }; | |
3335 | ||
3336 | (deref_mut_ || !by_mut_ref) && !by_box && !by_value | |
3337 | } else { | |
3338 | false | |
3339 | } | |
3340 | } | |
3341 | ||
ff7c6d11 XL |
3342 | fn render_spotlight_traits(item: &clean::Item) -> Result<String, fmt::Error> { |
3343 | let mut out = String::new(); | |
3344 | ||
3345 | match item.inner { | |
3346 | clean::FunctionItem(clean::Function { ref decl, .. }) | | |
3347 | clean::TyMethodItem(clean::TyMethod { ref decl, .. }) | | |
3348 | clean::MethodItem(clean::Method { ref decl, .. }) | | |
3349 | clean::ForeignFunctionItem(clean::Function { ref decl, .. }) => { | |
3350 | out = spotlight_decl(decl)?; | |
3351 | } | |
3352 | _ => {} | |
3353 | } | |
3354 | ||
3355 | Ok(out) | |
3356 | } | |
3357 | ||
3358 | fn spotlight_decl(decl: &clean::FnDecl) -> Result<String, fmt::Error> { | |
3359 | let mut out = String::new(); | |
3360 | let mut trait_ = String::new(); | |
3361 | ||
3362 | if let Some(did) = decl.output.def_id() { | |
3363 | let c = cache(); | |
3364 | if let Some(impls) = c.impls.get(&did) { | |
3365 | for i in impls { | |
3366 | let impl_ = i.inner_impl(); | |
2c00a5a8 | 3367 | if impl_.trait_.def_id().map_or(false, |d| c.traits[&d].is_spotlight) { |
ff7c6d11 XL |
3368 | if out.is_empty() { |
3369 | out.push_str( | |
3370 | &format!("<h3 class=\"important\">Important traits for {}</h3>\ | |
3371 | <code class=\"content\">", | |
3372 | impl_.for_)); | |
3373 | trait_.push_str(&format!("{}", impl_.for_)); | |
3374 | } | |
3375 | ||
3376 | //use the "where" class here to make it small | |
3377 | out.push_str(&format!("<span class=\"where fmt-newline\">{}</span>", impl_)); | |
3378 | let t_did = impl_.trait_.def_id().unwrap(); | |
3379 | for it in &impl_.items { | |
3380 | if let clean::TypedefItem(ref tydef, _) = it.inner { | |
3381 | out.push_str("<span class=\"where fmt-newline\"> "); | |
3382 | assoc_type(&mut out, it, &vec![], | |
3383 | Some(&tydef.type_), | |
3384 | AssocItemLink::GotoSource(t_did, &FxHashSet()))?; | |
3385 | out.push_str(";</span>"); | |
3386 | } | |
3387 | } | |
3388 | } | |
3389 | } | |
3390 | } | |
3391 | } | |
3392 | ||
3393 | if !out.is_empty() { | |
3394 | out.insert_str(0, &format!("<div class=\"important-traits\"><div class='tooltip'>ⓘ\ | |
3395 | <span class='tooltiptext'>Important traits for {}</span></div>\ | |
3396 | <div class=\"content hidden\">", | |
3397 | trait_)); | |
3398 | out.push_str("</code></div></div>"); | |
3399 | } | |
3400 | ||
3401 | Ok(out) | |
3402 | } | |
3403 | ||
e9174d1e | 3404 | fn render_impl(w: &mut fmt::Formatter, cx: &Context, i: &Impl, link: AssocItemLink, |
ea8adc8c XL |
3405 | render_mode: RenderMode, outer_version: Option<&str>, |
3406 | show_def_docs: bool) -> fmt::Result { | |
9e0c209e | 3407 | if render_mode == RenderMode::Normal { |
3b2f2976 | 3408 | let id = derive_id(match i.inner_impl().trait_ { |
abe05a73 | 3409 | Some(ref t) => format!("impl-{}", small_url_encode(&format!("{:#}", t))), |
3b2f2976 XL |
3410 | None => "impl".to_string(), |
3411 | }); | |
3412 | write!(w, "<h3 id='{}' class='impl'><span class='in-band'><code>{}</code>", | |
3413 | id, i.inner_impl())?; | |
3414 | write!(w, "<a href='#{}' class='anchor'></a>", id)?; | |
a7813a04 XL |
3415 | write!(w, "</span><span class='out-of-band'>")?; |
3416 | let since = i.impl_item.stability.as_ref().map(|s| &s.since[..]); | |
476ff2be | 3417 | if let Some(l) = (Item { item: &i.impl_item, cx: cx }).src_href() { |
a7813a04 XL |
3418 | write!(w, "<div class='ghost'></div>")?; |
3419 | render_stability_since_raw(w, since, outer_version)?; | |
476ff2be SL |
3420 | write!(w, "<a class='srclink' href='{}' title='{}'>[src]</a>", |
3421 | l, "goto source code")?; | |
a7813a04 XL |
3422 | } else { |
3423 | render_stability_since_raw(w, since, outer_version)?; | |
3424 | } | |
3425 | write!(w, "</span>")?; | |
3426 | write!(w, "</h3>\n")?; | |
ff7c6d11 | 3427 | if let Some(ref dox) = cx.shared.maybe_collapsed_doc_value(&i.impl_item) { |
2c00a5a8 XL |
3428 | write!(w, "<div class='docblock'>{}</div>", |
3429 | Markdown(&*dox, &i.impl_item.links(), cx.render_type))?; | |
d9579d0f | 3430 | } |
1a4d82fc JJ |
3431 | } |
3432 | ||
9e0c209e SL |
3433 | fn doc_impl_item(w: &mut fmt::Formatter, cx: &Context, item: &clean::Item, |
3434 | link: AssocItemLink, render_mode: RenderMode, | |
3435 | is_default_item: bool, outer_version: Option<&str>, | |
ea8adc8c | 3436 | trait_: Option<&clean::Trait>, show_def_docs: bool) -> fmt::Result { |
c30ab7b3 | 3437 | let item_type = item.type_(); |
92a42be0 | 3438 | let name = item.name.as_ref().unwrap(); |
54a0048b | 3439 | |
9e0c209e SL |
3440 | let render_method_item: bool = match render_mode { |
3441 | RenderMode::Normal => true, | |
abe05a73 | 3442 | RenderMode::ForDeref { mut_: deref_mut_ } => should_render_item(&item, deref_mut_), |
54a0048b SL |
3443 | }; |
3444 | ||
1a4d82fc | 3445 | match item.inner { |
ff7c6d11 XL |
3446 | clean::MethodItem(clean::Method { ref decl, .. }) | |
3447 | clean::TyMethodItem(clean::TyMethod{ ref decl, .. }) => { | |
62682a34 | 3448 | // Only render when the method is not static or we allow static methods |
9e0c209e SL |
3449 | if render_method_item { |
3450 | let id = derive_id(format!("{}.{}", item_type, name)); | |
3451 | let ns_id = derive_id(format!("{}.{}", name, item_type.name_space())); | |
8bb4bdeb | 3452 | write!(w, "<h4 id='{}' class=\"{}\">", id, item_type)?; |
ff7c6d11 | 3453 | write!(w, "{}", spotlight_decl(decl)?)?; |
9e0c209e | 3454 | write!(w, "<span id='{}' class='invisible'>", ns_id)?; |
54a0048b | 3455 | write!(w, "<code>")?; |
476ff2be | 3456 | render_assoc_item(w, item, link.anchor(&id), ItemType::Impl)?; |
a7813a04 | 3457 | write!(w, "</code>")?; |
3b2f2976 XL |
3458 | if let Some(l) = (Item { cx, item }).src_href() { |
3459 | write!(w, "</span><span class='out-of-band'>")?; | |
3460 | write!(w, "<div class='ghost'></div>")?; | |
3461 | render_stability_since_raw(w, item.stable_since(), outer_version)?; | |
3462 | write!(w, "<a class='srclink' href='{}' title='{}'>[src]</a>", | |
3463 | l, "goto source code")?; | |
3464 | } else { | |
3465 | render_stability_since_raw(w, item.stable_since(), outer_version)?; | |
3466 | } | |
9e0c209e | 3467 | write!(w, "</span></h4>\n")?; |
62682a34 | 3468 | } |
1a4d82fc | 3469 | } |
62682a34 | 3470 | clean::TypedefItem(ref tydef, _) => { |
54a0048b | 3471 | let id = derive_id(format!("{}.{}", ItemType::AssociatedType, name)); |
9e0c209e | 3472 | let ns_id = derive_id(format!("{}.{}", name, item_type.name_space())); |
8bb4bdeb | 3473 | write!(w, "<h4 id='{}' class=\"{}\">", id, item_type)?; |
9e0c209e | 3474 | write!(w, "<span id='{}' class='invisible'><code>", ns_id)?; |
a7813a04 | 3475 | assoc_type(w, item, &Vec::new(), Some(&tydef.type_), link.anchor(&id))?; |
9e0c209e | 3476 | write!(w, "</code></span></h4>\n")?; |
1a4d82fc | 3477 | } |
d9579d0f | 3478 | clean::AssociatedConstItem(ref ty, ref default) => { |
9e0c209e SL |
3479 | let id = derive_id(format!("{}.{}", item_type, name)); |
3480 | let ns_id = derive_id(format!("{}.{}", name, item_type.name_space())); | |
8bb4bdeb | 3481 | write!(w, "<h4 id='{}' class=\"{}\">", id, item_type)?; |
9e0c209e | 3482 | write!(w, "<span id='{}' class='invisible'><code>", ns_id)?; |
a7813a04 | 3483 | assoc_const(w, item, ty, default.as_ref(), link.anchor(&id))?; |
9e0c209e | 3484 | write!(w, "</code></span></h4>\n")?; |
d9579d0f | 3485 | } |
c34b1796 | 3486 | clean::AssociatedTypeItem(ref bounds, ref default) => { |
9e0c209e SL |
3487 | let id = derive_id(format!("{}.{}", item_type, name)); |
3488 | let ns_id = derive_id(format!("{}.{}", name, item_type.name_space())); | |
8bb4bdeb | 3489 | write!(w, "<h4 id='{}' class=\"{}\">", id, item_type)?; |
9e0c209e | 3490 | write!(w, "<span id='{}' class='invisible'><code>", ns_id)?; |
a7813a04 | 3491 | assoc_type(w, item, bounds, default.as_ref(), link.anchor(&id))?; |
9e0c209e | 3492 | write!(w, "</code></span></h4>\n")?; |
1a4d82fc | 3493 | } |
54a0048b | 3494 | clean::StrippedItem(..) => return Ok(()), |
1a4d82fc JJ |
3495 | _ => panic!("can't make docs for trait item with name {:?}", item.name) |
3496 | } | |
62682a34 | 3497 | |
9e0c209e | 3498 | if render_method_item || render_mode == RenderMode::Normal { |
041b39d2 | 3499 | let prefix = render_assoc_const_value(item); |
ff7c6d11 | 3500 | |
a7813a04 | 3501 | if !is_default_item { |
3157f602 XL |
3502 | if let Some(t) = trait_ { |
3503 | // The trait item may have been stripped so we might not | |
3504 | // find any documentation or stability for it. | |
3505 | if let Some(it) = t.items.iter().find(|i| i.name == item.name) { | |
3506 | // We need the stability of the item from the trait | |
3507 | // because impls can't have a stability. | |
3508 | document_stability(w, cx, it)?; | |
041b39d2 | 3509 | if item.doc_value().is_some() { |
ea8adc8c XL |
3510 | document_full(w, item, cx, &prefix)?; |
3511 | } else if show_def_docs { | |
3157f602 XL |
3512 | // In case the item isn't documented, |
3513 | // provide short documentation from the trait. | |
ea8adc8c | 3514 | document_short(w, it, link, cx, &prefix)?; |
a7813a04 XL |
3515 | } |
3516 | } | |
3157f602 | 3517 | } else { |
041b39d2 | 3518 | document_stability(w, cx, item)?; |
ea8adc8c XL |
3519 | if show_def_docs { |
3520 | document_full(w, item, cx, &prefix)?; | |
3521 | } | |
a7813a04 XL |
3522 | } |
3523 | } else { | |
3157f602 | 3524 | document_stability(w, cx, item)?; |
ea8adc8c XL |
3525 | if show_def_docs { |
3526 | document_short(w, item, link, cx, &prefix)?; | |
3527 | } | |
a7813a04 | 3528 | } |
1a4d82fc | 3529 | } |
a7813a04 | 3530 | Ok(()) |
1a4d82fc JJ |
3531 | } |
3532 | ||
a7813a04 | 3533 | let traits = &cache().traits; |
2c00a5a8 | 3534 | let trait_ = i.trait_did().map(|did| &traits[&did]); |
a7813a04 | 3535 | |
ea8adc8c XL |
3536 | if !show_def_docs { |
3537 | write!(w, "<span class='docblock autohide'>")?; | |
3538 | } | |
3539 | ||
54a0048b | 3540 | write!(w, "<div class='impl-items'>")?; |
a7813a04 | 3541 | for trait_item in &i.inner_impl().items { |
9e0c209e | 3542 | doc_impl_item(w, cx, trait_item, link, render_mode, |
ea8adc8c | 3543 | false, outer_version, trait_, show_def_docs)?; |
1a4d82fc JJ |
3544 | } |
3545 | ||
d9579d0f | 3546 | fn render_default_items(w: &mut fmt::Formatter, |
e9174d1e | 3547 | cx: &Context, |
d9579d0f | 3548 | t: &clean::Trait, |
54a0048b | 3549 | i: &clean::Impl, |
9e0c209e | 3550 | render_mode: RenderMode, |
ea8adc8c XL |
3551 | outer_version: Option<&str>, |
3552 | show_def_docs: bool) -> fmt::Result { | |
85aaf69f | 3553 | for trait_item in &t.items { |
c34b1796 | 3554 | let n = trait_item.name.clone(); |
54a0048b SL |
3555 | if i.items.iter().find(|m| m.name == n).is_some() { |
3556 | continue; | |
1a4d82fc | 3557 | } |
54a0048b SL |
3558 | let did = i.trait_.as_ref().unwrap().def_id().unwrap(); |
3559 | let assoc_link = AssocItemLink::GotoSource(did, &i.provided_trait_methods); | |
1a4d82fc | 3560 | |
9e0c209e | 3561 | doc_impl_item(w, cx, trait_item, assoc_link, render_mode, true, |
ea8adc8c | 3562 | outer_version, None, show_def_docs)?; |
1a4d82fc JJ |
3563 | } |
3564 | Ok(()) | |
3565 | } | |
3566 | ||
3567 | // If we've implemented a trait, then also emit documentation for all | |
54a0048b | 3568 | // default items which weren't overridden in the implementation block. |
a7813a04 | 3569 | if let Some(t) = trait_ { |
ea8adc8c XL |
3570 | render_default_items(w, cx, t, &i.inner_impl(), |
3571 | render_mode, outer_version, show_def_docs)?; | |
1a4d82fc | 3572 | } |
54a0048b | 3573 | write!(w, "</div>")?; |
ea8adc8c XL |
3574 | |
3575 | if !show_def_docs { | |
3576 | write!(w, "</span>")?; | |
3577 | } | |
3578 | ||
1a4d82fc JJ |
3579 | Ok(()) |
3580 | } | |
3581 | ||
e9174d1e | 3582 | fn item_typedef(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, |
1a4d82fc | 3583 | t: &clean::Typedef) -> fmt::Result { |
8bb4bdeb XL |
3584 | write!(w, "<pre class='rust typedef'>")?; |
3585 | render_attributes(w, it)?; | |
3586 | write!(w, "type {}{}{where_clause} = {type_};</pre>", | |
54a0048b SL |
3587 | it.name.as_ref().unwrap(), |
3588 | t.generics, | |
cc61c64b | 3589 | where_clause = WhereClause { gens: &t.generics, indent: 0, end_newline: true }, |
54a0048b | 3590 | type_ = t.type_)?; |
1a4d82fc | 3591 | |
041b39d2 XL |
3592 | document(w, cx, it)?; |
3593 | ||
3594 | // Render any items associated directly to this alias, as otherwise they | |
3595 | // won't be visible anywhere in the docs. It would be nice to also show | |
3596 | // associated items from the aliased type (see discussion in #32077), but | |
3597 | // we need #14072 to make sense of the generics. | |
3598 | render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) | |
1a4d82fc JJ |
3599 | } |
3600 | ||
abe05a73 XL |
3601 | fn item_foreign_type(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item) -> fmt::Result { |
3602 | writeln!(w, "<pre class='rust foreigntype'>extern {{")?; | |
3603 | render_attributes(w, it)?; | |
3604 | write!( | |
3605 | w, | |
3606 | " {}type {};\n}}</pre>", | |
3607 | VisSpace(&it.visibility), | |
3608 | it.name.as_ref().unwrap(), | |
3609 | )?; | |
3610 | ||
3611 | document(w, cx, it)?; | |
3612 | ||
3613 | render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) | |
3614 | } | |
3615 | ||
85aaf69f | 3616 | impl<'a> fmt::Display for Sidebar<'a> { |
1a4d82fc JJ |
3617 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
3618 | let cx = self.cx; | |
3619 | let it = self.item; | |
c34b1796 | 3620 | let parentlen = cx.current.len() - if it.is_mod() {1} else {0}; |
ff7c6d11 | 3621 | let mut should_close = false; |
c34b1796 | 3622 | |
cc61c64b | 3623 | if it.is_struct() || it.is_trait() || it.is_primitive() || it.is_union() |
041b39d2 | 3624 | || it.is_enum() || it.is_mod() || it.is_typedef() |
cc61c64b XL |
3625 | { |
3626 | write!(fmt, "<p class='location'>")?; | |
3627 | match it.inner { | |
3628 | clean::StructItem(..) => write!(fmt, "Struct ")?, | |
3629 | clean::TraitItem(..) => write!(fmt, "Trait ")?, | |
3630 | clean::PrimitiveItem(..) => write!(fmt, "Primitive Type ")?, | |
3631 | clean::UnionItem(..) => write!(fmt, "Union ")?, | |
3632 | clean::EnumItem(..) => write!(fmt, "Enum ")?, | |
041b39d2 | 3633 | clean::TypedefItem(..) => write!(fmt, "Type Definition ")?, |
abe05a73 | 3634 | clean::ForeignTypeItem => write!(fmt, "Foreign Type ")?, |
cc61c64b XL |
3635 | clean::ModuleItem(..) => if it.is_crate() { |
3636 | write!(fmt, "Crate ")?; | |
3637 | } else { | |
3638 | write!(fmt, "Module ")?; | |
3639 | }, | |
3640 | _ => (), | |
3641 | } | |
3642 | write!(fmt, "{}", it.name.as_ref().unwrap())?; | |
3643 | write!(fmt, "</p>")?; | |
3644 | ||
abe05a73 XL |
3645 | if it.is_crate() { |
3646 | if let Some(ref version) = cache().crate_version { | |
3647 | write!(fmt, | |
3648 | "<div class='block version'>\ | |
3649 | <p>Version {}</p>\ | |
3650 | </div>", | |
3651 | version)?; | |
3652 | } | |
3653 | } | |
3654 | ||
ff7c6d11 XL |
3655 | write!(fmt, "<div class=\"sidebar-elems\">")?; |
3656 | should_close = true; | |
cc61c64b XL |
3657 | match it.inner { |
3658 | clean::StructItem(ref s) => sidebar_struct(fmt, it, s)?, | |
3659 | clean::TraitItem(ref t) => sidebar_trait(fmt, it, t)?, | |
3660 | clean::PrimitiveItem(ref p) => sidebar_primitive(fmt, it, p)?, | |
3661 | clean::UnionItem(ref u) => sidebar_union(fmt, it, u)?, | |
3662 | clean::EnumItem(ref e) => sidebar_enum(fmt, it, e)?, | |
041b39d2 | 3663 | clean::TypedefItem(ref t, _) => sidebar_typedef(fmt, it, t)?, |
cc61c64b | 3664 | clean::ModuleItem(ref m) => sidebar_module(fmt, it, &m.items)?, |
abe05a73 | 3665 | clean::ForeignTypeItem => sidebar_foreign_type(fmt, it)?, |
cc61c64b XL |
3666 | _ => (), |
3667 | } | |
3668 | } | |
3669 | ||
476ff2be | 3670 | // The sidebar is designed to display sibling functions, modules and |
5bcae85e | 3671 | // other miscellaneous information. since there are lots of sibling |
c34b1796 AL |
3672 | // items (and that causes quadratic growth in large modules), |
3673 | // we refactor common parts into a shared JavaScript file per module. | |
3674 | // still, we don't move everything into JS because we want to preserve | |
3675 | // as much HTML as possible in order to allow non-JS-enabled browsers | |
3676 | // to navigate the documentation (though slightly inefficiently). | |
3677 | ||
54a0048b | 3678 | write!(fmt, "<p class='location'>")?; |
c34b1796 | 3679 | for (i, name) in cx.current.iter().take(parentlen).enumerate() { |
1a4d82fc | 3680 | if i > 0 { |
54a0048b | 3681 | write!(fmt, "::<wbr>")?; |
1a4d82fc | 3682 | } |
54a0048b | 3683 | write!(fmt, "<a href='{}index.html'>{}</a>", |
c30ab7b3 | 3684 | &cx.root_path()[..(cx.current.len() - i - 1) * 3], |
54a0048b | 3685 | *name)?; |
1a4d82fc | 3686 | } |
54a0048b | 3687 | write!(fmt, "</p>")?; |
1a4d82fc | 3688 | |
476ff2be | 3689 | // Sidebar refers to the enclosing module, not this module. |
54a0048b SL |
3690 | let relpath = if it.is_mod() { "../" } else { "" }; |
3691 | write!(fmt, | |
3692 | "<script>window.sidebarCurrent = {{\ | |
3693 | name: '{name}', \ | |
3694 | ty: '{ty}', \ | |
3695 | relpath: '{path}'\ | |
3696 | }};</script>", | |
3697 | name = it.name.as_ref().map(|x| &x[..]).unwrap_or(""), | |
c30ab7b3 | 3698 | ty = it.type_().css_class(), |
54a0048b | 3699 | path = relpath)?; |
c34b1796 | 3700 | if parentlen == 0 { |
476ff2be | 3701 | // There is no sidebar-items.js beyond the crate root path |
c34b1796 AL |
3702 | // FIXME maybe dynamic crate loading can be merged here |
3703 | } else { | |
54a0048b SL |
3704 | write!(fmt, "<script defer src=\"{path}sidebar-items.js\"></script>", |
3705 | path = relpath)?; | |
1a4d82fc | 3706 | } |
ff7c6d11 XL |
3707 | if should_close { |
3708 | // Closes sidebar-elems div. | |
3709 | write!(fmt, "</div>")?; | |
3710 | } | |
1a4d82fc | 3711 | |
1a4d82fc JJ |
3712 | Ok(()) |
3713 | } | |
3714 | } | |
3715 | ||
abe05a73 XL |
3716 | fn get_methods(i: &clean::Impl, for_deref: bool) -> Vec<String> { |
3717 | i.items.iter().filter_map(|item| { | |
3718 | match item.name { | |
3719 | // Maybe check with clean::Visibility::Public as well? | |
3720 | Some(ref name) if !name.is_empty() && item.visibility.is_some() && item.is_method() => { | |
3721 | if !for_deref || should_render_item(item, false) { | |
3722 | Some(format!("<a href=\"#method.{name}\">{name}</a>", name = name)) | |
3723 | } else { | |
3724 | None | |
3725 | } | |
3726 | } | |
3727 | _ => None, | |
3728 | } | |
3729 | }).collect::<Vec<_>>() | |
3730 | } | |
3731 | ||
3732 | // The point is to url encode any potential character from a type with genericity. | |
3733 | fn small_url_encode(s: &str) -> String { | |
3734 | s.replace("<", "%3C") | |
3735 | .replace(">", "%3E") | |
3736 | .replace(" ", "%20") | |
3737 | .replace("?", "%3F") | |
3738 | .replace("'", "%27") | |
3739 | .replace("&", "%26") | |
3740 | .replace(",", "%2C") | |
3741 | .replace(":", "%3A") | |
3742 | .replace(";", "%3B") | |
3743 | .replace("[", "%5B") | |
3744 | .replace("]", "%5D") | |
ff7c6d11 | 3745 | .replace("\"", "%22") |
abe05a73 XL |
3746 | } |
3747 | ||
cc61c64b XL |
3748 | fn sidebar_assoc_items(it: &clean::Item) -> String { |
3749 | let mut out = String::new(); | |
3750 | let c = cache(); | |
3751 | if let Some(v) = c.impls.get(&it.def_id) { | |
abe05a73 XL |
3752 | let ret = v.iter() |
3753 | .filter(|i| i.inner_impl().trait_.is_none()) | |
3754 | .flat_map(|i| get_methods(i.inner_impl(), false)) | |
3755 | .collect::<String>(); | |
3756 | if !ret.is_empty() { | |
3757 | out.push_str(&format!("<a class=\"sidebar-title\" href=\"#methods\">Methods\ | |
3758 | </a><div class=\"sidebar-links\">{}</div>", ret)); | |
cc61c64b XL |
3759 | } |
3760 | ||
3761 | if v.iter().any(|i| i.inner_impl().trait_.is_some()) { | |
3762 | if let Some(impl_) = v.iter() | |
3763 | .filter(|i| i.inner_impl().trait_.is_some()) | |
3764 | .find(|i| i.inner_impl().trait_.def_id() == c.deref_trait_did) { | |
3765 | if let Some(target) = impl_.inner_impl().items.iter().filter_map(|item| { | |
3766 | match item.inner { | |
3767 | clean::TypedefItem(ref t, true) => Some(&t.type_), | |
3768 | _ => None, | |
3769 | } | |
3770 | }).next() { | |
3771 | let inner_impl = target.def_id().or(target.primitive_type().and_then(|prim| { | |
3772 | c.primitive_locations.get(&prim).cloned() | |
3773 | })).and_then(|did| c.impls.get(&did)); | |
abe05a73 XL |
3774 | if let Some(impls) = inner_impl { |
3775 | out.push_str("<a class=\"sidebar-title\" href=\"#deref-methods\">"); | |
ff7c6d11 XL |
3776 | out.push_str(&format!("Methods from {}<Target={}>", |
3777 | Escape(&format!("{:#}", | |
3778 | impl_.inner_impl().trait_.as_ref().unwrap())), | |
3779 | Escape(&format!("{:#}", target)))); | |
abe05a73 XL |
3780 | out.push_str("</a>"); |
3781 | let ret = impls.iter() | |
3782 | .filter(|i| i.inner_impl().trait_.is_none()) | |
3783 | .flat_map(|i| get_methods(i.inner_impl(), true)) | |
3784 | .collect::<String>(); | |
3785 | out.push_str(&format!("<div class=\"sidebar-links\">{}</div>", ret)); | |
cc61c64b XL |
3786 | } |
3787 | } | |
3788 | } | |
abe05a73 XL |
3789 | let mut links = HashSet::new(); |
3790 | let ret = v.iter() | |
ff7c6d11 XL |
3791 | .filter_map(|i| { |
3792 | let is_negative_impl = is_negative_impl(i.inner_impl()); | |
3793 | if let Some(ref i) = i.inner_impl().trait_ { | |
3794 | let i_display = format!("{:#}", i); | |
3795 | let out = Escape(&i_display); | |
3796 | let encoded = small_url_encode(&format!("{:#}", i)); | |
3797 | let generated = format!("<a href=\"#impl-{}\">{}{}</a>", | |
3798 | encoded, | |
3799 | if is_negative_impl { "!" } else { "" }, | |
3800 | out); | |
3801 | if !links.contains(&generated) && links.insert(generated.clone()) { | |
3802 | Some(generated) | |
3803 | } else { | |
3804 | None | |
3805 | } | |
abe05a73 XL |
3806 | } else { |
3807 | None | |
3808 | } | |
abe05a73 XL |
3809 | }) |
3810 | .collect::<String>(); | |
3811 | if !ret.is_empty() { | |
3812 | out.push_str("<a class=\"sidebar-title\" href=\"#implementations\">\ | |
3813 | Trait Implementations</a>"); | |
3814 | out.push_str(&format!("<div class=\"sidebar-links\">{}</div>", ret)); | |
3815 | } | |
cc61c64b XL |
3816 | } |
3817 | } | |
3818 | ||
3819 | out | |
3820 | } | |
3821 | ||
3822 | fn sidebar_struct(fmt: &mut fmt::Formatter, it: &clean::Item, | |
3823 | s: &clean::Struct) -> fmt::Result { | |
3824 | let mut sidebar = String::new(); | |
abe05a73 | 3825 | let fields = get_struct_fields_name(&s.fields); |
cc61c64b | 3826 | |
abe05a73 | 3827 | if !fields.is_empty() { |
cc61c64b | 3828 | if let doctree::Plain = s.struct_type { |
abe05a73 XL |
3829 | sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#fields\">Fields</a>\ |
3830 | <div class=\"sidebar-links\">{}</div>", fields)); | |
cc61c64b XL |
3831 | } |
3832 | } | |
3833 | ||
3834 | sidebar.push_str(&sidebar_assoc_items(it)); | |
3835 | ||
3836 | if !sidebar.is_empty() { | |
abe05a73 | 3837 | write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?; |
cc61c64b XL |
3838 | } |
3839 | Ok(()) | |
3840 | } | |
3841 | ||
abe05a73 XL |
3842 | fn extract_for_impl_name(item: &clean::Item) -> Option<(String, String)> { |
3843 | match item.inner { | |
3844 | clean::ItemEnum::ImplItem(ref i) => { | |
3845 | if let Some(ref trait_) = i.trait_ { | |
3846 | Some((format!("{:#}", i.for_), format!("{:#}", trait_))) | |
3847 | } else { | |
3848 | None | |
3849 | } | |
3850 | }, | |
3851 | _ => None, | |
3852 | } | |
3853 | } | |
3854 | ||
ff7c6d11 XL |
3855 | fn is_negative_impl(i: &clean::Impl) -> bool { |
3856 | i.polarity == Some(clean::ImplPolarity::Negative) | |
3857 | } | |
3858 | ||
cc61c64b XL |
3859 | fn sidebar_trait(fmt: &mut fmt::Formatter, it: &clean::Item, |
3860 | t: &clean::Trait) -> fmt::Result { | |
3861 | let mut sidebar = String::new(); | |
3862 | ||
abe05a73 XL |
3863 | let types = t.items |
3864 | .iter() | |
3865 | .filter_map(|m| { | |
3866 | match m.name { | |
3867 | Some(ref name) if m.is_associated_type() => { | |
3868 | Some(format!("<a href=\"#associatedtype.{name}\">{name}</a>", | |
3869 | name=name)) | |
3870 | } | |
3871 | _ => None, | |
3872 | } | |
3873 | }) | |
3874 | .collect::<String>(); | |
3875 | let consts = t.items | |
3876 | .iter() | |
3877 | .filter_map(|m| { | |
3878 | match m.name { | |
3879 | Some(ref name) if m.is_associated_const() => { | |
3880 | Some(format!("<a href=\"#associatedconstant.{name}\">{name}</a>", | |
3881 | name=name)) | |
3882 | } | |
3883 | _ => None, | |
3884 | } | |
3885 | }) | |
3886 | .collect::<String>(); | |
3887 | let required = t.items | |
3888 | .iter() | |
3889 | .filter_map(|m| { | |
3890 | match m.name { | |
3891 | Some(ref name) if m.is_ty_method() => { | |
3892 | Some(format!("<a href=\"#tymethod.{name}\">{name}</a>", | |
3893 | name=name)) | |
3894 | } | |
3895 | _ => None, | |
3896 | } | |
3897 | }) | |
3898 | .collect::<String>(); | |
3899 | let provided = t.items | |
3900 | .iter() | |
3901 | .filter_map(|m| { | |
3902 | match m.name { | |
3903 | Some(ref name) if m.is_method() => { | |
3904 | Some(format!("<a href=\"#method.{name}\">{name}</a>", name=name)) | |
3905 | } | |
3906 | _ => None, | |
3907 | } | |
3908 | }) | |
3909 | .collect::<String>(); | |
cc61c64b | 3910 | |
abe05a73 XL |
3911 | if !types.is_empty() { |
3912 | sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#associated-types\">\ | |
3913 | Associated Types</a><div class=\"sidebar-links\">{}</div>", | |
3914 | types)); | |
cc61c64b | 3915 | } |
abe05a73 XL |
3916 | if !consts.is_empty() { |
3917 | sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#associated-const\">\ | |
3918 | Associated Constants</a><div class=\"sidebar-links\">{}</div>", | |
3919 | consts)); | |
cc61c64b | 3920 | } |
abe05a73 XL |
3921 | if !required.is_empty() { |
3922 | sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#required-methods\">\ | |
3923 | Required Methods</a><div class=\"sidebar-links\">{}</div>", | |
3924 | required)); | |
cc61c64b | 3925 | } |
abe05a73 XL |
3926 | if !provided.is_empty() { |
3927 | sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#provided-methods\">\ | |
3928 | Provided Methods</a><div class=\"sidebar-links\">{}</div>", | |
3929 | provided)); | |
cc61c64b XL |
3930 | } |
3931 | ||
ea8adc8c XL |
3932 | let c = cache(); |
3933 | ||
3934 | if let Some(implementors) = c.implementors.get(&it.def_id) { | |
abe05a73 | 3935 | let res = implementors.iter() |
2c00a5a8 XL |
3936 | .filter(|i| i.inner_impl().for_.def_id() |
3937 | .map_or(false, |d| !c.paths.contains_key(&d))) | |
abe05a73 | 3938 | .filter_map(|i| { |
2c00a5a8 XL |
3939 | match extract_for_impl_name(&i.impl_item) { |
3940 | Some((ref name, ref url)) => { | |
3941 | Some(format!("<a href=\"#impl-{}\">{}</a>", | |
3942 | small_url_encode(url), | |
3943 | Escape(name))) | |
abe05a73 | 3944 | } |
2c00a5a8 | 3945 | _ => None, |
abe05a73 XL |
3946 | } |
3947 | }) | |
3948 | .collect::<String>(); | |
3949 | if !res.is_empty() { | |
3950 | sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#foreign-impls\">\ | |
3951 | Implementations on Foreign Types</a><div \ | |
3952 | class=\"sidebar-links\">{}</div>", | |
3953 | res)); | |
3954 | } | |
3955 | } | |
3956 | ||
3957 | sidebar.push_str("<a class=\"sidebar-title\" href=\"#implementors\">Implementors</a>"); | |
ea8adc8c | 3958 | |
abe05a73 | 3959 | sidebar.push_str(&sidebar_assoc_items(it)); |
cc61c64b | 3960 | |
abe05a73 | 3961 | write!(fmt, "<div class=\"block items\">{}</div>", sidebar) |
cc61c64b XL |
3962 | } |
3963 | ||
3964 | fn sidebar_primitive(fmt: &mut fmt::Formatter, it: &clean::Item, | |
3965 | _p: &clean::PrimitiveType) -> fmt::Result { | |
3966 | let sidebar = sidebar_assoc_items(it); | |
3967 | ||
3968 | if !sidebar.is_empty() { | |
abe05a73 | 3969 | write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?; |
cc61c64b XL |
3970 | } |
3971 | Ok(()) | |
3972 | } | |
3973 | ||
041b39d2 XL |
3974 | fn sidebar_typedef(fmt: &mut fmt::Formatter, it: &clean::Item, |
3975 | _t: &clean::Typedef) -> fmt::Result { | |
3976 | let sidebar = sidebar_assoc_items(it); | |
3977 | ||
3978 | if !sidebar.is_empty() { | |
abe05a73 | 3979 | write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?; |
041b39d2 XL |
3980 | } |
3981 | Ok(()) | |
3982 | } | |
3983 | ||
abe05a73 XL |
3984 | fn get_struct_fields_name(fields: &[clean::Item]) -> String { |
3985 | fields.iter() | |
3986 | .filter(|f| if let clean::StructFieldItem(..) = f.inner { | |
3987 | true | |
3988 | } else { | |
3989 | false | |
3990 | }) | |
3991 | .filter_map(|f| match f.name { | |
3992 | Some(ref name) => Some(format!("<a href=\"#structfield.{name}\">\ | |
3993 | {name}</a>", name=name)), | |
3994 | _ => None, | |
3995 | }) | |
3996 | .collect() | |
3997 | } | |
3998 | ||
cc61c64b XL |
3999 | fn sidebar_union(fmt: &mut fmt::Formatter, it: &clean::Item, |
4000 | u: &clean::Union) -> fmt::Result { | |
4001 | let mut sidebar = String::new(); | |
abe05a73 | 4002 | let fields = get_struct_fields_name(&u.fields); |
cc61c64b | 4003 | |
abe05a73 XL |
4004 | if !fields.is_empty() { |
4005 | sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#fields\">Fields</a>\ | |
4006 | <div class=\"sidebar-links\">{}</div>", fields)); | |
cc61c64b XL |
4007 | } |
4008 | ||
4009 | sidebar.push_str(&sidebar_assoc_items(it)); | |
4010 | ||
4011 | if !sidebar.is_empty() { | |
abe05a73 | 4012 | write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?; |
cc61c64b XL |
4013 | } |
4014 | Ok(()) | |
4015 | } | |
4016 | ||
4017 | fn sidebar_enum(fmt: &mut fmt::Formatter, it: &clean::Item, | |
4018 | e: &clean::Enum) -> fmt::Result { | |
4019 | let mut sidebar = String::new(); | |
4020 | ||
abe05a73 XL |
4021 | let variants = e.variants.iter() |
4022 | .filter_map(|v| match v.name { | |
4023 | Some(ref name) => Some(format!("<a href=\"#variant.{name}\">{name}\ | |
4024 | </a>", name = name)), | |
4025 | _ => None, | |
4026 | }) | |
4027 | .collect::<String>(); | |
4028 | if !variants.is_empty() { | |
4029 | sidebar.push_str(&format!("<a class=\"sidebar-title\" href=\"#variants\">Variants</a>\ | |
4030 | <div class=\"sidebar-links\">{}</div>", variants)); | |
cc61c64b XL |
4031 | } |
4032 | ||
4033 | sidebar.push_str(&sidebar_assoc_items(it)); | |
4034 | ||
4035 | if !sidebar.is_empty() { | |
abe05a73 | 4036 | write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?; |
cc61c64b XL |
4037 | } |
4038 | Ok(()) | |
4039 | } | |
4040 | ||
4041 | fn sidebar_module(fmt: &mut fmt::Formatter, _it: &clean::Item, | |
4042 | items: &[clean::Item]) -> fmt::Result { | |
4043 | let mut sidebar = String::new(); | |
4044 | ||
4045 | if items.iter().any(|it| it.type_() == ItemType::ExternCrate || | |
4046 | it.type_() == ItemType::Import) { | |
4047 | sidebar.push_str(&format!("<li><a href=\"#{id}\">{name}</a></li>", | |
4048 | id = "reexports", | |
2c00a5a8 | 4049 | name = "Re-exports")); |
cc61c64b XL |
4050 | } |
4051 | ||
4052 | // ordering taken from item_module, reorder, where it prioritized elements in a certain order | |
4053 | // to print its headings | |
4054 | for &myty in &[ItemType::Primitive, ItemType::Module, ItemType::Macro, ItemType::Struct, | |
4055 | ItemType::Enum, ItemType::Constant, ItemType::Static, ItemType::Trait, | |
4056 | ItemType::Function, ItemType::Typedef, ItemType::Union, ItemType::Impl, | |
4057 | ItemType::TyMethod, ItemType::Method, ItemType::StructField, ItemType::Variant, | |
abe05a73 | 4058 | ItemType::AssociatedType, ItemType::AssociatedConst, ItemType::ForeignType] { |
2c00a5a8 | 4059 | if items.iter().any(|it| !it.is_stripped() && it.type_() == myty) { |
cc61c64b XL |
4060 | let (short, name) = match myty { |
4061 | ItemType::ExternCrate | | |
2c00a5a8 | 4062 | ItemType::Import => ("reexports", "Re-exports"), |
cc61c64b XL |
4063 | ItemType::Module => ("modules", "Modules"), |
4064 | ItemType::Struct => ("structs", "Structs"), | |
4065 | ItemType::Union => ("unions", "Unions"), | |
4066 | ItemType::Enum => ("enums", "Enums"), | |
4067 | ItemType::Function => ("functions", "Functions"), | |
4068 | ItemType::Typedef => ("types", "Type Definitions"), | |
4069 | ItemType::Static => ("statics", "Statics"), | |
4070 | ItemType::Constant => ("constants", "Constants"), | |
4071 | ItemType::Trait => ("traits", "Traits"), | |
4072 | ItemType::Impl => ("impls", "Implementations"), | |
4073 | ItemType::TyMethod => ("tymethods", "Type Methods"), | |
4074 | ItemType::Method => ("methods", "Methods"), | |
4075 | ItemType::StructField => ("fields", "Struct Fields"), | |
4076 | ItemType::Variant => ("variants", "Variants"), | |
4077 | ItemType::Macro => ("macros", "Macros"), | |
4078 | ItemType::Primitive => ("primitives", "Primitive Types"), | |
4079 | ItemType::AssociatedType => ("associated-types", "Associated Types"), | |
4080 | ItemType::AssociatedConst => ("associated-consts", "Associated Constants"), | |
abe05a73 | 4081 | ItemType::ForeignType => ("foreign-types", "Foreign Types"), |
cc61c64b XL |
4082 | }; |
4083 | sidebar.push_str(&format!("<li><a href=\"#{id}\">{name}</a></li>", | |
4084 | id = short, | |
4085 | name = name)); | |
4086 | } | |
4087 | } | |
4088 | ||
4089 | if !sidebar.is_empty() { | |
4090 | write!(fmt, "<div class=\"block items\"><ul>{}</ul></div>", sidebar)?; | |
4091 | } | |
4092 | Ok(()) | |
4093 | } | |
4094 | ||
abe05a73 XL |
4095 | fn sidebar_foreign_type(fmt: &mut fmt::Formatter, it: &clean::Item) -> fmt::Result { |
4096 | let sidebar = sidebar_assoc_items(it); | |
4097 | if !sidebar.is_empty() { | |
4098 | write!(fmt, "<div class=\"block items\">{}</div>", sidebar)?; | |
4099 | } | |
4100 | Ok(()) | |
4101 | } | |
4102 | ||
85aaf69f | 4103 | impl<'a> fmt::Display for Source<'a> { |
1a4d82fc JJ |
4104 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { |
4105 | let Source(s) = *self; | |
4106 | let lines = s.lines().count(); | |
4107 | let mut cols = 0; | |
4108 | let mut tmp = lines; | |
4109 | while tmp > 0 { | |
4110 | cols += 1; | |
4111 | tmp /= 10; | |
4112 | } | |
54a0048b | 4113 | write!(fmt, "<pre class=\"line-numbers\">")?; |
85aaf69f | 4114 | for i in 1..lines + 1 { |
54a0048b | 4115 | write!(fmt, "<span id=\"{0}\">{0:1$}</span>\n", i, cols)?; |
1a4d82fc | 4116 | } |
54a0048b | 4117 | write!(fmt, "</pre>")?; |
ea8adc8c XL |
4118 | write!(fmt, "{}", |
4119 | highlight::render_with_highlighting(s, None, None, None, None))?; | |
1a4d82fc JJ |
4120 | Ok(()) |
4121 | } | |
4122 | } | |
4123 | ||
e9174d1e | 4124 | fn item_macro(w: &mut fmt::Formatter, cx: &Context, it: &clean::Item, |
1a4d82fc | 4125 | t: &clean::Macro) -> fmt::Result { |
54a0048b SL |
4126 | w.write_str(&highlight::render_with_highlighting(&t.source, |
4127 | Some("macro"), | |
9e0c209e | 4128 | None, |
ea8adc8c | 4129 | None, |
54a0048b | 4130 | None))?; |
e9174d1e | 4131 | document(w, cx, it) |
1a4d82fc JJ |
4132 | } |
4133 | ||
e9174d1e | 4134 | fn item_primitive(w: &mut fmt::Formatter, cx: &Context, |
1a4d82fc JJ |
4135 | it: &clean::Item, |
4136 | _p: &clean::PrimitiveType) -> fmt::Result { | |
54a0048b | 4137 | document(w, cx, it)?; |
7453a54e | 4138 | render_assoc_items(w, cx, it, it.def_id, AssocItemRender::All) |
1a4d82fc JJ |
4139 | } |
4140 | ||
54a0048b | 4141 | const BASIC_KEYWORDS: &'static str = "rust, rustlang, rust-lang"; |
1a4d82fc JJ |
4142 | |
4143 | fn make_item_keywords(it: &clean::Item) -> String { | |
54a0048b | 4144 | format!("{}, {}", BASIC_KEYWORDS, it.name.as_ref().unwrap()) |
1a4d82fc JJ |
4145 | } |
4146 | ||
a7813a04 XL |
4147 | fn get_index_search_type(item: &clean::Item) -> Option<IndexItemFunctionType> { |
4148 | let decl = match item.inner { | |
4149 | clean::FunctionItem(ref f) => &f.decl, | |
4150 | clean::MethodItem(ref m) => &m.decl, | |
4151 | clean::TyMethodItem(ref m) => &m.decl, | |
c34b1796 AL |
4152 | _ => return None |
4153 | }; | |
4154 | ||
a7813a04 | 4155 | let inputs = decl.inputs.values.iter().map(|arg| get_index_type(&arg.type_)).collect(); |
c34b1796 AL |
4156 | let output = match decl.output { |
4157 | clean::FunctionRetTy::Return(ref return_type) => Some(get_index_type(return_type)), | |
4158 | _ => None | |
4159 | }; | |
4160 | ||
4161 | Some(IndexItemFunctionType { inputs: inputs, output: output }) | |
4162 | } | |
4163 | ||
4164 | fn get_index_type(clean_type: &clean::Type) -> Type { | |
abe05a73 XL |
4165 | let t = Type { |
4166 | name: get_index_type_name(clean_type, true).map(|s| s.to_ascii_lowercase()), | |
4167 | generics: get_generics(clean_type), | |
4168 | }; | |
4169 | t | |
c34b1796 AL |
4170 | } |
4171 | ||
abe05a73 | 4172 | fn get_index_type_name(clean_type: &clean::Type, accept_generic: bool) -> Option<String> { |
c34b1796 AL |
4173 | match *clean_type { |
4174 | clean::ResolvedPath { ref path, .. } => { | |
4175 | let segments = &path.segments; | |
2c00a5a8 XL |
4176 | let path_segment = segments.into_iter().last().unwrap_or_else(|| panic!( |
4177 | "get_index_type_name(clean_type: {:?}, accept_generic: {:?}) had length zero path", | |
4178 | clean_type, accept_generic | |
4179 | )); | |
4180 | Some(path_segment.name.clone()) | |
abe05a73 XL |
4181 | } |
4182 | clean::Generic(ref s) if accept_generic => Some(s.clone()), | |
c34b1796 | 4183 | clean::Primitive(ref p) => Some(format!("{:?}", p)), |
abe05a73 | 4184 | clean::BorrowedRef { ref type_, .. } => get_index_type_name(type_, accept_generic), |
c34b1796 AL |
4185 | // FIXME: add all from clean::Type. |
4186 | _ => None | |
4187 | } | |
4188 | } | |
4189 | ||
abe05a73 XL |
4190 | fn get_generics(clean_type: &clean::Type) -> Option<Vec<String>> { |
4191 | clean_type.generics() | |
4192 | .and_then(|types| { | |
4193 | let r = types.iter() | |
4194 | .filter_map(|t| get_index_type_name(t, false)) | |
4195 | .map(|s| s.to_ascii_lowercase()) | |
4196 | .collect::<Vec<_>>(); | |
4197 | if r.is_empty() { | |
4198 | None | |
4199 | } else { | |
4200 | Some(r) | |
4201 | } | |
4202 | }) | |
4203 | } | |
4204 | ||
1a4d82fc JJ |
4205 | pub fn cache() -> Arc<Cache> { |
4206 | CACHE_KEY.with(|c| c.borrow().clone()) | |
4207 | } | |
92a42be0 SL |
4208 | |
4209 | #[cfg(test)] | |
4210 | #[test] | |
4211 | fn test_unique_id() { | |
4212 | let input = ["foo", "examples", "examples", "method.into_iter","examples", | |
4213 | "method.into_iter", "foo", "main", "search", "methods", | |
4214 | "examples", "method.into_iter", "assoc_type.Item", "assoc_type.Item"]; | |
4215 | let expected = ["foo", "examples", "examples-1", "method.into_iter", "examples-2", | |
4216 | "method.into_iter-1", "foo-1", "main-1", "search-1", "methods-1", | |
4217 | "examples-3", "method.into_iter-2", "assoc_type.Item", "assoc_type.Item-1"]; | |
4218 | ||
4219 | let test = || { | |
4220 | let actual: Vec<String> = input.iter().map(|s| derive_id(s.to_string())).collect(); | |
4221 | assert_eq!(&actual[..], expected); | |
4222 | }; | |
4223 | test(); | |
54a0048b | 4224 | reset_ids(true); |
92a42be0 SL |
4225 | test(); |
4226 | } | |
cc61c64b XL |
4227 | |
4228 | #[cfg(test)] | |
4229 | #[test] | |
4230 | fn test_name_key() { | |
4231 | assert_eq!(name_key("0"), ("", 0, 1)); | |
4232 | assert_eq!(name_key("123"), ("", 123, 0)); | |
4233 | assert_eq!(name_key("Fruit"), ("Fruit", 0, 0)); | |
4234 | assert_eq!(name_key("Fruit0"), ("Fruit", 0, 1)); | |
4235 | assert_eq!(name_key("Fruit0000"), ("Fruit", 0, 4)); | |
4236 | assert_eq!(name_key("Fruit01"), ("Fruit", 1, 1)); | |
4237 | assert_eq!(name_key("Fruit10"), ("Fruit", 10, 0)); | |
4238 | assert_eq!(name_key("Fruit123"), ("Fruit", 123, 0)); | |
4239 | } | |
4240 | ||
4241 | #[cfg(test)] | |
4242 | #[test] | |
4243 | fn test_name_sorting() { | |
4244 | let names = ["Apple", | |
4245 | "Banana", | |
4246 | "Fruit", "Fruit0", "Fruit00", | |
4247 | "Fruit1", "Fruit01", | |
4248 | "Fruit2", "Fruit02", | |
4249 | "Fruit20", | |
4250 | "Fruit100", | |
4251 | "Pear"]; | |
4252 | let mut sorted = names.to_owned(); | |
4253 | sorted.sort_by_key(|&s| name_key(s)); | |
4254 | assert_eq!(names, sorted); | |
4255 | } |