From 19f9f658d64c4cb3eae674db677aabbe9b41cb0f Mon Sep 17 00:00:00 2001
From: Michael Howell
Date: Mon, 3 Apr 2023 17:12:19 -0700
Subject: [PATCH] rustdoc: migrate document functions to return a Display
---
src/librustdoc/html/render/mod.rs | 282 ++++++++++++-----------
src/librustdoc/html/render/print_item.rs | 49 ++--
src/librustdoc/html/sources.rs | 6 +-
3 files changed, 178 insertions(+), 159 deletions(-)
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 920fbdcf0d6..49986433dfb 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -70,7 +70,7 @@
use crate::formats::{AssocItemRender, Impl, RenderMode};
use crate::html::escape::Escape;
use crate::html::format::{
- href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
+ display_fn, href, join_with_double_colon, print_abi_with_space, print_constness_with_space,
print_default_space, print_generic_bounds, print_where_clause, visibility_print_with_space,
Buffer, Ending, HrefError, PrintWithSpace,
};
@@ -409,128 +409,134 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
)
}
-fn document(
- w: &mut Buffer,
- cx: &mut Context<'_>,
- item: &clean::Item,
- parent: Option<&clean::Item>,
+fn document<'a, 'cx: 'a>(
+ cx: &'a mut Context<'cx>,
+ item: &'a clean::Item,
+ parent: Option<&'a clean::Item>,
heading_offset: HeadingOffset,
-) {
+) -> impl fmt::Display + 'a + Captures<'cx> {
if let Some(ref name) = item.name {
info!("Documenting {}", name);
}
- document_item_info(cx, item, parent).render_into(w).unwrap();
- if parent.is_none() {
- document_full_collapsible(w, item, cx, heading_offset);
- } else {
- document_full(w, item, cx, heading_offset);
- }
+
+ display_fn(move |f| {
+ document_item_info(cx, item, parent).render_into(f).unwrap();
+ if parent.is_none() {
+ write!(f, "{}", document_full_collapsible(item, cx, heading_offset))?;
+ } else {
+ write!(f, "{}", document_full(item, cx, heading_offset))?;
+ }
+ Ok(())
+ })
}
/// Render md_text as markdown.
-fn render_markdown(
- w: &mut Buffer,
- cx: &mut Context<'_>,
- md_text: &str,
+fn render_markdown<'a, 'cx: 'a>(
+ cx: &'a mut Context<'cx>,
+ md_text: &'a str,
links: Vec,
heading_offset: HeadingOffset,
-) {
- write!(
- w,
- "{}
",
- Markdown {
- content: md_text,
- links: &links,
- ids: &mut cx.id_map,
- error_codes: cx.shared.codes,
- edition: cx.shared.edition(),
- playground: &cx.shared.playground,
- heading_offset,
- }
- .into_string()
- )
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ display_fn(move |f| {
+ write!(
+ f,
+ "{}
",
+ Markdown {
+ content: md_text,
+ links: &links,
+ ids: &mut cx.id_map,
+ error_codes: cx.shared.codes,
+ edition: cx.shared.edition(),
+ playground: &cx.shared.playground,
+ heading_offset,
+ }
+ .into_string()
+ )
+ })
}
/// Writes a documentation block containing only the first paragraph of the documentation. If the
/// docs are longer, a "Read more" link is appended to the end.
-fn document_short(
- w: &mut Buffer,
- item: &clean::Item,
- cx: &mut Context<'_>,
- link: AssocItemLink<'_>,
- parent: &clean::Item,
+fn document_short<'a, 'cx: 'a>(
+ item: &'a clean::Item,
+ cx: &'a mut Context<'cx>,
+ link: AssocItemLink<'a>,
+ parent: &'a clean::Item,
show_def_docs: bool,
-) {
- document_item_info(cx, item, Some(parent)).render_into(w).unwrap();
- if !show_def_docs {
- return;
- }
- if let Some(s) = item.doc_value() {
- let (mut summary_html, has_more_content) =
- MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content();
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ display_fn(move |f| {
+ document_item_info(cx, item, Some(parent)).render_into(f).unwrap();
+ if !show_def_docs {
+ return Ok(());
+ }
+ if let Some(s) = item.doc_value() {
+ let (mut summary_html, has_more_content) =
+ MarkdownSummaryLine(&s, &item.links(cx)).into_string_with_has_more_content();
- if has_more_content {
- let link = format!(r#" Read more "#, assoc_href_attr(item, link, cx));
+ if has_more_content {
+ let link = format!(r#" Read more "#, assoc_href_attr(item, link, cx));
- if let Some(idx) = summary_html.rfind("
") {
- summary_html.insert_str(idx, &link);
+ if let Some(idx) = summary_html.rfind("") {
+ summary_html.insert_str(idx, &link);
+ } else {
+ summary_html.push_str(&link);
+ }
+ }
+
+ write!(f, "{}
", summary_html)?;
+ }
+ Ok(())
+ })
+}
+
+fn document_full_collapsible<'a, 'cx: 'a>(
+ item: &'a clean::Item,
+ cx: &'a mut Context<'cx>,
+ heading_offset: HeadingOffset,
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ document_full_inner(item, cx, true, heading_offset)
+}
+
+fn document_full<'a, 'cx: 'a>(
+ item: &'a clean::Item,
+ cx: &'a mut Context<'cx>,
+ heading_offset: HeadingOffset,
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ document_full_inner(item, cx, false, heading_offset)
+}
+
+fn document_full_inner<'a, 'cx: 'a>(
+ item: &'a clean::Item,
+ cx: &'a mut Context<'cx>,
+ is_collapsible: bool,
+ heading_offset: HeadingOffset,
+) -> impl fmt::Display + 'a + Captures<'cx> {
+ display_fn(move |f| {
+ if let Some(s) = item.collapsed_doc_value() {
+ debug!("Doc block: =====\n{}\n=====", s);
+ if is_collapsible {
+ write!(
+ f,
+ "\
+ \
+ Expand description \
+ {} ",
+ render_markdown(cx, &s, item.links(cx), heading_offset)
+ )?;
} else {
- summary_html.push_str(&link);
+ write!(f, "{}", render_markdown(cx, &s, item.links(cx), heading_offset))?;
}
}
- write!(w, "{}
", summary_html,);
- }
-}
+ let kind = match &*item.kind {
+ clean::ItemKind::StrippedItem(box kind) | kind => kind,
+ };
-fn document_full_collapsible(
- w: &mut Buffer,
- item: &clean::Item,
- cx: &mut Context<'_>,
- heading_offset: HeadingOffset,
-) {
- document_full_inner(w, item, cx, true, heading_offset);
-}
-
-fn document_full(
- w: &mut Buffer,
- item: &clean::Item,
- cx: &mut Context<'_>,
- heading_offset: HeadingOffset,
-) {
- document_full_inner(w, item, cx, false, heading_offset);
-}
-
-fn document_full_inner(
- w: &mut Buffer,
- item: &clean::Item,
- cx: &mut Context<'_>,
- is_collapsible: bool,
- heading_offset: HeadingOffset,
-) {
- if let Some(s) = item.collapsed_doc_value() {
- debug!("Doc block: =====\n{}\n=====", s);
- if is_collapsible {
- w.write_str(
- "\
- \
- Expand description \
- ",
- );
- render_markdown(w, cx, &s, item.links(cx), heading_offset);
- w.write_str(" ");
- } else {
- render_markdown(w, cx, &s, item.links(cx), heading_offset);
+ if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind {
+ render_call_locations(f, cx, item);
}
- }
-
- let kind = match &*item.kind {
- clean::ItemKind::StrippedItem(box kind) | kind => kind,
- };
-
- if let clean::ItemKind::FunctionItem(..) | clean::ItemKind::MethodItem(..) = kind {
- render_call_locations(w, cx, item);
- }
+ Ok(())
+ })
}
#[derive(Template)]
@@ -1485,18 +1491,25 @@ fn doc_impl_item(
document_item_info(cx, it, Some(parent))
.render_into(&mut info_buffer)
.unwrap();
- document_full(&mut doc_buffer, item, cx, HeadingOffset::H5);
+ write!(
+ &mut doc_buffer,
+ "{}",
+ document_full(item, cx, HeadingOffset::H5)
+ );
short_documented = false;
} else {
// In case the item isn't documented,
// provide short documentation from the trait.
- document_short(
+ write!(
&mut doc_buffer,
- it,
- cx,
- link,
- parent,
- rendering_params.show_def_docs,
+ "{}",
+ document_short(
+ it,
+ cx,
+ link,
+ parent,
+ rendering_params.show_def_docs,
+ )
);
}
}
@@ -1505,18 +1518,15 @@ fn doc_impl_item(
.render_into(&mut info_buffer)
.unwrap();
if rendering_params.show_def_docs {
- document_full(&mut doc_buffer, item, cx, HeadingOffset::H5);
+ write!(&mut doc_buffer, "{}", document_full(item, cx, HeadingOffset::H5));
short_documented = false;
}
}
} else {
- document_short(
+ write!(
&mut doc_buffer,
- item,
- cx,
- link,
- parent,
- rendering_params.show_def_docs,
+ "{}",
+ document_short(item, cx, link, parent, rendering_params.show_def_docs,)
);
}
}
@@ -2213,7 +2223,7 @@ fn collect_paths_for_type(first_ty: clean::Type, cache: &Cache) -> Vec {
const NUM_VISIBLE_LINES: usize = 10;
/// Generates the HTML for example call locations generated via the --scrape-examples flag.
-fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item) {
+fn render_call_locations(mut w: W, cx: &mut Context<'_>, item: &clean::Item) {
let tcx = cx.tcx();
let def_id = item.item_id.expect_def_id();
let key = tcx.def_path_hash(def_id);
@@ -2222,7 +2232,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
// Generate a unique ID so users can link to this section for a given method
let id = cx.id_map.derive("scraped-examples");
write!(
- w,
+ &mut w,
"\
\
\
@@ -2231,7 +2241,8 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
",
root_path = cx.root_path(),
id = id
- );
+ )
+ .unwrap();
// Create a URL to a particular location in a reverse-dependency's source file
let link_to_loc = |call_data: &CallData, loc: &CallLocation| -> (String, String) {
@@ -2249,7 +2260,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
};
// Generate the HTML for a single example, being the title and code block
- let write_example = |w: &mut Buffer, (path, call_data): (&PathBuf, &CallData)| -> bool {
+ let write_example = |mut w: &mut W, (path, call_data): (&PathBuf, &CallData)| -> bool {
let contents = match fs::read_to_string(&path) {
Ok(contents) => contents,
Err(err) => {
@@ -2297,7 +2308,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
let locations_encoded = serde_json::to_string(&line_ranges).unwrap();
write!(
- w,
+ &mut w,
"
\
\
{name} (
{title} )\
@@ -2310,10 +2321,12 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
// The locations are encoded as a data attribute, so they can be read
// later by the JS for interactions.
locations = Escape(&locations_encoded)
- );
+ )
+ .unwrap();
if line_ranges.len() > 1 {
- write!(w, r#"
≺ ≻ "#);
+ write!(w, r#"
≺ ≻ "#)
+ .unwrap();
}
// Look for the example file in the source map if it exists, otherwise return a dummy span
@@ -2340,7 +2353,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
decoration_info.insert("highlight", byte_ranges);
sources::print_src(
- w,
+ &mut w,
contents_subset,
file_span,
cx,
@@ -2348,7 +2361,7 @@ fn render_call_locations(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Ite
highlight::DecorationInfo(decoration_info),
sources::SourceContext::Embedded { offset: line_min, needs_expansion },
);
- write!(w, "
");
+ write!(w, "
").unwrap();
true
};
@@ -2382,7 +2395,7 @@ fn sort_criterion<'a>(
// An example may fail to write if its source can't be read for some reason, so this method
// continues iterating until a write succeeds
- let write_and_skip_failure = |w: &mut Buffer, it: &mut Peekable<_>| {
+ let write_and_skip_failure = |w: &mut W, it: &mut Peekable<_>| {
while let Some(example) = it.next() {
if write_example(&mut *w, example) {
break;
@@ -2391,7 +2404,7 @@ fn sort_criterion<'a>(
};
// Write just one example that's visible by default in the method's description.
- write_and_skip_failure(w, &mut it);
+ write_and_skip_failure(&mut w, &mut it);
// Then add the remaining examples in a hidden section.
if it.peek().is_some() {
@@ -2404,17 +2417,19 @@ fn sort_criterion<'a>(
Hide additional examples
\
\
"
- );
+ )
+ .unwrap();
// Only generate inline code for MAX_FULL_EXAMPLES number of examples. Otherwise we could
// make the page arbitrarily huge!
for _ in 0..MAX_FULL_EXAMPLES {
- write_and_skip_failure(w, &mut it);
+ write_and_skip_failure(&mut w, &mut it);
}
// For the remaining examples, generate a
");
+ write!(w, "").unwrap();
}
- write!(w, "");
+ write!(w, "").unwrap();
}
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index b223c49a899..ea53f8d107d 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -218,7 +218,7 @@ fn toggle_close(w: &mut Buffer) {
}
fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: &[clean::Item]) {
- document(w, cx, item, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, item, None, HeadingOffset::H2));
let mut indices = (0..items.len()).filter(|i| !items[*i].is_stripped()).collect::>();
@@ -562,7 +562,7 @@ fn item_function(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, f: &cle
notable_traits = notable_traits.unwrap_or_default(),
);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
}
fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Trait) {
@@ -717,7 +717,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean:
});
// Trait documentation
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) {
write!(
@@ -735,7 +735,7 @@ fn trait_item(w: &mut Buffer, cx: &mut Context<'_>, m: &clean::Item, t: &clean::
let item_type = m.type_();
let id = cx.derive_id(format!("{}.{}", item_type, name));
let mut content = Buffer::empty_from(w);
- document(&mut content, cx, m, Some(t), HeadingOffset::H5);
+ write!(&mut content, "{}", document(cx, m, Some(t), HeadingOffset::H5));
let toggled = !content.is_empty();
if toggled {
let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" };
@@ -1068,7 +1068,7 @@ fn item_trait_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &
);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
@@ -1090,7 +1090,7 @@ fn item_opaque_ty(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &cl
);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
// Render any items associated directly to this alias, as otherwise they
// won't be visible anywhere in the docs. It would be nice to also show
@@ -1117,7 +1117,7 @@ fn write_content(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::
write_content(w, cx, it, t);
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
let def_id = it.item_id.expect_def_id();
// Render any items associated directly to this alias, as otherwise they
@@ -1134,7 +1134,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
render_union(w, it, Some(&s.generics), &s.fields, cx);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
let mut fields = s
.fields
@@ -1166,7 +1166,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
if let Some(stability_class) = field.stability_class(cx.tcx()) {
write!(w, " ");
}
- document(w, cx, field, Some(it), HeadingOffset::H3);
+ write!(w, "{}", document(cx, field, Some(it), HeadingOffset::H3));
}
}
let def_id = it.item_id.expect_def_id();
@@ -1248,7 +1248,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
}
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
if count_variants != 0 {
write!(
@@ -1324,10 +1324,13 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
{f}: {t}
\
",
f = field.name.unwrap(),
- t = ty.print(cx)
+ t = ty.print(cx),
+ );
+ write!(
+ w,
+ "{}",
+ document(cx, field, Some(variant), HeadingOffset::H5)
);
- document(w, cx, field, Some(variant), HeadingOffset::H5);
- write!(w, "");
}
_ => unreachable!(),
}
@@ -1335,7 +1338,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
w.write_str("");
}
- document(w, cx, variant, Some(it), HeadingOffset::H4);
+ write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4));
}
write!(w, "");
}
@@ -1346,7 +1349,7 @@ fn item_enum(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, e: &clean::
fn item_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::Macro) {
highlight::render_item_decl_with_highlighting(&t.source, w);
- document(w, cx, it, None, HeadingOffset::H2)
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &clean::ProcMacro) {
@@ -1372,12 +1375,12 @@ fn item_proc_macro(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, m: &c
}
}
});
- document(w, cx, it, None, HeadingOffset::H2)
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
fn item_primitive(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
let def_id = it.item_id.expect_def_id();
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) {
render_assoc_items(w, cx, it, def_id, AssocItemRender::All);
} else {
@@ -1435,7 +1438,7 @@ fn item_constant(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, c: &cle
}
});
- document(w, cx, it, None, HeadingOffset::H2)
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean::Struct) {
@@ -1444,7 +1447,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
render_struct(w, it, Some(&s.generics), s.ctor_kind, &s.fields, "", true, cx);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
let mut fields = s
.fields
@@ -1478,7 +1481,7 @@ fn item_struct(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
item_type = ItemType::StructField,
ty = ty.print(cx)
);
- document(w, cx, field, Some(it), HeadingOffset::H3);
+ write!(w, "{}", document(cx, field, Some(it), HeadingOffset::H3));
}
}
}
@@ -1499,7 +1502,7 @@ fn item_static(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean
typ = s.type_.print(cx)
);
});
- document(w, cx, it, None, HeadingOffset::H2)
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
@@ -1514,13 +1517,13 @@ fn item_foreign_type(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
);
});
- document(w, cx, it, None, HeadingOffset::H2);
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2));
render_assoc_items(w, cx, it, it.item_id.expect_def_id(), AssocItemRender::All)
}
fn item_keyword(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item) {
- document(w, cx, it, None, HeadingOffset::H2)
+ write!(w, "{}", document(cx, it, None, HeadingOffset::H2))
}
/// Compare two strings treating multi-digit numbers as single units (i.e. natural sort order).
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index 1d298f52f75..c8397967c87 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -2,7 +2,6 @@
use crate::docfs::PathError;
use crate::error::Error;
use crate::html::format;
-use crate::html::format::Buffer;
use crate::html::highlight;
use crate::html::layout;
use crate::html::render::Context;
@@ -17,6 +16,7 @@
use std::cell::RefCell;
use std::ffi::OsStr;
+use std::fmt;
use std::fs;
use std::ops::RangeInclusive;
use std::path::{Component, Path, PathBuf};
@@ -294,7 +294,7 @@ pub(crate) enum SourceContext {
/// Wrapper struct to render the source code of a file. This will do things like
/// adding line numbers to the left-hand side.
pub(crate) fn print_src(
- buf: &mut Buffer,
+ mut writer: impl fmt::Write,
s: &str,
file_span: rustc_span::Span,
context: &Context<'_>,
@@ -329,5 +329,5 @@ struct Source {
);
Ok(())
});
- Source { embedded, needs_expansion, lines, code_html: code }.render_into(buf).unwrap();
+ Source { embedded, needs_expansion, lines, code_html: code }.render_into(&mut writer).unwrap();
}