Răsfoiți Sursa

feat: enable children properly

Jonathan Kelley 3 ani în urmă
părinte
comite
b997b8ebbb

+ 1 - 1
packages/core-macro/src/lib.rs

@@ -17,7 +17,7 @@ pub fn format_args_f(input: TokenStream) -> TokenStream {
         .into()
 }
 
-#[proc_macro_derive(Props, attributes(builder))]
+#[proc_macro_derive(Props, attributes(props))]
 pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let input = parse_macro_input!(input as syn::DeriveInput);
     match props::impl_my_derive(&input) {

+ 19 - 5
packages/core-macro/src/props/mod.rs

@@ -3,8 +3,8 @@
 //! However, it has been adopted to fit the Dioxus Props builder pattern.
 //!
 //! For dioxus, we make a few changes:
-//! - automatically implement Into<Option> on the setters (IE the strip setter option)
-//! - automatically implement a default of none for optional fields (those explicitly wrapped with Option<T>)
+//! - [ ] automatically implement Into<Option> on the setters (IE the strip setter option)
+//! - [ ] automatically implement a default of none for optional fields (those explicitly wrapped with Option<T>)
 
 use proc_macro2::TokenStream;
 
@@ -194,6 +194,14 @@ mod field_info {
             field_defaults: FieldBuilderAttr,
         ) -> Result<FieldInfo, Error> {
             if let Some(ref name) = field.ident {
+                let mut builder_attr = field_defaults.with(&field.attrs)?;
+
+                // children field is automatically defaulted to None
+                if name == "children" {
+                    builder_attr.default =
+                        Some(syn::parse(quote!(Default::default()).into()).unwrap());
+                }
+
                 Ok(FieldInfo {
                     ordinal,
                     name,
@@ -202,7 +210,7 @@ mod field_info {
                         proc_macro2::Span::call_site(),
                     ),
                     ty: &field.ty,
-                    builder_attr: field_defaults.with(&field.attrs)?,
+                    builder_attr,
                 })
             } else {
                 Err(Error::new(field.span(), "Nameless field in struct"))
@@ -285,7 +293,7 @@ mod field_info {
         pub fn with(mut self, attrs: &[syn::Attribute]) -> Result<Self, Error> {
             let mut skip_tokens = None;
             for attr in attrs {
-                if path_to_single_string(&attr.path).as_deref() != Some("builder") {
+                if path_to_single_string(&attr.path).as_deref() != Some("props") {
                     continue;
                 }
 
@@ -316,7 +324,7 @@ mod field_info {
             if self.setter.skip && self.default.is_none() {
                 return Err(Error::new_spanned(
                     skip_tokens.unwrap(),
-                    "#[builder(skip)] must be accompanied by default or default_code",
+                    "#[props(skip)] must be accompanied by default or default_code",
                 ));
             }
 
@@ -325,6 +333,7 @@ mod field_info {
 
         pub fn apply_meta(&mut self, expr: syn::Expr) -> Result<(), Error> {
             match expr {
+                // #[props(default = "...")]
                 syn::Expr::Assign(assign) => {
                     let name = expr_to_single_string(&assign.left)
                         .ok_or_else(|| Error::new_spanned(&assign.left, "Expected identifier"))?;
@@ -356,6 +365,8 @@ mod field_info {
                         )),
                     }
                 }
+
+                // uh not sure
                 syn::Expr::Path(path) => {
                     let name = path_to_single_string(&path.path)
                         .ok_or_else(|| Error::new_spanned(&path, "Expected identifier"))?;
@@ -371,6 +382,8 @@ mod field_info {
                         )),
                     }
                 }
+
+                //
                 syn::Expr::Call(call) => {
                     let subsetting_name = if let syn::Expr::Path(path) = &*call.func {
                         path_to_single_string(&path.path)
@@ -398,6 +411,7 @@ mod field_info {
                         )),
                     }
                 }
+
                 syn::Expr::Unary(syn::ExprUnary {
                     op: syn::UnOp::Not(_),
                     expr,

+ 40 - 0
packages/core/examples/component_children.rs

@@ -0,0 +1,40 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+
+fn main() {
+    let mut dom = VirtualDom::new(parent);
+    let edits = dom.rebuild();
+    dbg!(edits);
+}
+
+fn parent(cx: Scope<()>) -> Element {
+    let value = cx.use_hook(|_| String::new(), |f| f);
+
+    cx.render(rsx! {
+        div {
+            child(
+                name: value,
+                h1 {"hi"}
+            )
+        }
+    })
+}
+
+#[derive(Props)]
+struct ChildProps<'a> {
+    name: &'a str,
+    children: Element<'a>,
+}
+
+fn child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
+    cx.render(rsx! {
+        div {
+            "it's nested {cx.props.name}"
+            {&cx.props.children}
+        }
+    })
+}

+ 23 - 25
packages/core/src/diff.rs

@@ -88,8 +88,6 @@
 //! More info on how to improve this diffing algorithm:
 //!  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
 
-use std::borrow::BorrowMut;
-
 use crate::innerlude::*;
 use fxhash::{FxHashMap, FxHashSet};
 use smallvec::{smallvec, SmallVec};
@@ -295,7 +293,7 @@ impl<'bump> DiffState<'bump> {
                 for child in el.children.iter() {
                     num_on_stack += self.push_all_nodes(child);
                 }
-                self.mutations.push_root(el.dom_id.get().unwrap());
+                self.mutations.push_root(el.id.get().unwrap());
 
                 num_on_stack + 1
             }
@@ -351,7 +349,7 @@ impl<'bump> DiffState<'bump> {
         let real_id = self.scopes.reserve_node(node);
 
         self.mutations.create_text_node(vtext.text, real_id);
-        vtext.dom_id.set(Some(real_id));
+        vtext.id.set(Some(real_id));
         self.stack.add_child_count(1);
     }
 
@@ -359,20 +357,20 @@ impl<'bump> DiffState<'bump> {
         let real_id = self.scopes.reserve_node(node);
 
         self.mutations.create_placeholder(real_id);
-        anchor.dom_id.set(Some(real_id));
+        anchor.id.set(Some(real_id));
 
         self.stack.add_child_count(1);
     }
 
     fn create_element_node(&mut self, element: &'bump VElement<'bump>, node: &'bump VNode<'bump>) {
         let VElement {
-            tag_name,
+            tag: tag_name,
             listeners,
             attributes,
             children,
             namespace,
-            dom_id,
-            parent_id,
+            id: dom_id,
+            parent: parent_id,
             ..
         } = element;
 
@@ -479,9 +477,9 @@ impl<'bump> DiffState<'bump> {
             }
             (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
             (Placeholder(old), Placeholder(new)) => {
-                if let Some(root) = old.dom_id.get() {
+                if let Some(root) = old.id.get() {
                     self.scopes.update_node(new_node, root);
-                    new.dom_id.set(Some(root))
+                    new.id.set(Some(root))
                 }
             }
 
@@ -515,13 +513,13 @@ impl<'bump> DiffState<'bump> {
         _old_node: &'bump VNode<'bump>,
         new_node: &'bump VNode<'bump>,
     ) {
-        if let Some(root) = old.dom_id.get() {
+        if let Some(root) = old.id.get() {
             if old.text != new.text {
                 self.mutations.set_text(new.text, root.as_u64());
             }
             self.scopes.update_node(new_node, root);
 
-            new.dom_id.set(Some(root));
+            new.id.set(Some(root));
         }
     }
 
@@ -532,13 +530,13 @@ impl<'bump> DiffState<'bump> {
         old_node: &'bump VNode<'bump>,
         new_node: &'bump VNode<'bump>,
     ) {
-        let root = old.dom_id.get().unwrap();
+        let root = old.id.get().unwrap();
 
         // If the element type is completely different, the element needs to be re-rendered completely
         // This is an optimization React makes due to how users structure their code
         //
         // This case is rather rare (typically only in non-keyed lists)
-        if new.tag_name != old.tag_name || new.namespace != old.namespace {
+        if new.tag != old.tag || new.namespace != old.namespace {
             // maybe make this an instruction?
             // issue is that we need the "vnode" but this method only has the velement
             self.stack.push_nodes_created(0);
@@ -551,8 +549,8 @@ impl<'bump> DiffState<'bump> {
 
         self.scopes.update_node(new_node, root);
 
-        new.dom_id.set(Some(root));
-        new.parent_id.set(old.parent_id.get());
+        new.id.set(Some(root));
+        new.parent.set(old.parent.get());
 
         // todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the
         // element to modify its attributes.
@@ -1182,9 +1180,9 @@ impl<'bump> DiffState<'bump> {
 
         loop {
             match &search_node.take().unwrap() {
-                VNode::Text(t) => break t.dom_id.get(),
-                VNode::Element(t) => break t.dom_id.get(),
-                VNode::Placeholder(t) => break t.dom_id.get(),
+                VNode::Text(t) => break t.id.get(),
+                VNode::Element(t) => break t.id.get(),
+                VNode::Placeholder(t) => break t.id.get(),
                 VNode::Fragment(frag) => {
                     search_node = frag.children.last();
                 }
@@ -1209,9 +1207,9 @@ impl<'bump> DiffState<'bump> {
                     let scope_id = el.scope.get().unwrap();
                     search_node = Some(self.scopes.root_node(scope_id));
                 }
-                VNode::Text(t) => break t.dom_id.get(),
-                VNode::Element(t) => break t.dom_id.get(),
-                VNode::Placeholder(t) => break t.dom_id.get(),
+                VNode::Text(t) => break t.id.get(),
+                VNode::Element(t) => break t.id.get(),
+                VNode::Placeholder(t) => break t.id.get(),
             }
         }
     }
@@ -1263,7 +1261,7 @@ impl<'bump> DiffState<'bump> {
             match node {
                 VNode::Text(t) => {
                     // this check exists because our null node will be removed but does not have an ID
-                    if let Some(id) = t.dom_id.get() {
+                    if let Some(id) = t.id.get() {
                         self.scopes.collect_garbage(id);
 
                         if gen_muts {
@@ -1272,7 +1270,7 @@ impl<'bump> DiffState<'bump> {
                     }
                 }
                 VNode::Placeholder(a) => {
-                    let id = a.dom_id.get().unwrap();
+                    let id = a.id.get().unwrap();
                     self.scopes.collect_garbage(id);
 
                     if gen_muts {
@@ -1280,7 +1278,7 @@ impl<'bump> DiffState<'bump> {
                     }
                 }
                 VNode::Element(e) => {
-                    let id = e.dom_id.get().unwrap();
+                    let id = e.id.get().unwrap();
 
                     if gen_muts {
                         self.mutations.remove(id.as_u64());

+ 0 - 1
packages/core/src/lib.rs

@@ -16,7 +16,6 @@ pub(crate) mod innerlude {
     pub use crate::mutations::*;
     pub use crate::nodes::*;
     pub use crate::scopes::*;
-    pub(crate) use crate::scopes::*;
     pub use crate::virtual_dom::*;
 
     /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].

+ 27 - 80
packages/core/src/nodes.rs

@@ -141,9 +141,9 @@ impl<'src> VNode<'src> {
     /// Returns None if the VNode is not mounted, or if the VNode cannot be presented by a mounted ID (Fragment/Component)
     pub fn try_mounted_id(&self) -> Option<ElementId> {
         match &self {
-            VNode::Text(el) => el.dom_id.get(),
-            VNode::Element(el) => el.dom_id.get(),
-            VNode::Placeholder(el) => el.dom_id.get(),
+            VNode::Text(el) => el.id.get(),
+            VNode::Element(el) => el.id.get(),
+            VNode::Placeholder(el) => el.id.get(),
             VNode::Fragment(_) => None,
             VNode::Component(_) => None,
         }
@@ -176,7 +176,7 @@ impl Debug for VNode<'_> {
         match &self {
             VNode::Element(el) => s
                 .debug_struct("VNode::VElement")
-                .field("name", &el.tag_name)
+                .field("name", &el.tag)
                 .field("key", &el.key)
                 .field("attrs", &el.attributes)
                 .field("children", &el.children)
@@ -187,7 +187,6 @@ impl Debug for VNode<'_> {
                 write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
             }
             VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
-            // VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
         }
     }
 }
@@ -216,13 +215,13 @@ fn empty_cell() -> Cell<Option<ElementId>> {
 
 /// A placeholder node only generated when Fragments don't have any children.
 pub struct VPlaceholder {
-    pub dom_id: Cell<Option<ElementId>>,
+    pub id: Cell<Option<ElementId>>,
 }
 
 /// A bump-allocated string slice and metadata.
 pub struct VText<'src> {
     pub text: &'src str,
-    pub dom_id: Cell<Option<ElementId>>,
+    pub id: Cell<Option<ElementId>>,
     pub is_static: bool,
 }
 
@@ -238,11 +237,11 @@ pub struct VFragment<'src> {
 
 /// An element like a "div" with children, listeners, and attributes.
 pub struct VElement<'a> {
-    pub tag_name: &'static str,
+    pub tag: &'static str,
     pub namespace: Option<&'static str>,
     pub key: Option<&'a str>,
-    pub dom_id: Cell<Option<ElementId>>,
-    pub parent_id: Cell<Option<ElementId>>,
+    pub id: Cell<Option<ElementId>>,
+    pub parent: Cell<Option<ElementId>>,
     pub listeners: &'a [Listener<'a>],
     pub attributes: &'a [Attribute<'a>],
     pub children: &'a [VNode<'a>],
@@ -251,11 +250,11 @@ pub struct VElement<'a> {
 impl Debug for VElement<'_> {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("VElement")
-            .field("tag_name", &self.tag_name)
+            .field("tag_name", &self.tag)
             .field("namespace", &self.namespace)
             .field("key", &self.key)
-            .field("dom_id", &self.dom_id)
-            .field("parent_id", &self.parent_id)
+            .field("id", &self.id)
+            .field("parent", &self.parent)
             .field("listeners", &self.listeners.len())
             .field("attributes", &self.attributes)
             .field("children", &self.children)
@@ -363,20 +362,13 @@ pub struct VComponent<'src> {
 }
 
 pub(crate) struct VComponentProps<P> {
-    // Props start local
-    // If they are static, then they can get promoted onto the heap
     pub render_fn: Component<P>,
-    pub props: P,
     pub memo: unsafe fn(&P, &P) -> bool,
+    pub props: P,
 }
 
 pub trait AnyProps {
     fn as_ptr(&self) -> *const ();
-    fn release(&self);
-    fn replace_heap(&self, new: Box<dyn Any>);
-
-    // move the props from the bump arena onto the heap
-    fn promote(&self);
     fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>;
     unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
 }
@@ -384,16 +376,9 @@ pub trait AnyProps {
 impl<P> AnyProps for VComponentProps<P> {
     fn as_ptr(&self) -> *const () {
         &self.props as *const _ as *const ()
-        // // todo: maybe move this into an enum
-        // let heap_props = self.heap_props.borrow();
-        // if let Some(b) = heap_props.as_ref() {
-        //     b.as_ref() as *const _ as *const ()
-        // } else {
-        //     let local_props = self.local_props.borrow();
-        //     local_props.as_ref().unwrap() as *const _ as *const ()
-        // }
     }
 
+    // Safety:
     // this will downcat the other ptr as our swallowed type!
     // you *must* make this check *before* calling this method
     // if your functions are not the same, then you will downcast a pointer into a different type (UB)
@@ -403,41 +388,9 @@ impl<P> AnyProps for VComponentProps<P> {
         (self.memo)(real_us, real_other)
     }
 
-    fn release(&self) {
-        panic!("don't release props anymore");
-        // if let Some(heap_props) = self.heap_props.borrow_mut().take() {
-        //     dbg!("releasing heap");
-        //     drop(heap_props)
-        // } else if let Some(local_props) = self.local_props.borrow_mut().take() {
-        //     dbg!("releasing local");
-        //     drop(local_props)
-        // } else {
-        //     dbg!("trying to do a double release");
-        // }
-    }
-
-    fn promote(&self) {
-        panic!("don't promote props anymore");
-        // dbg!("promoting props");
-        // if let Some(props) = self.local_props.borrow_mut().take() {
-        //     let mut heap_slot = self.heap_props.borrow_mut();
-        //     *heap_slot = Some(Box::new(props));
-        // }
-    }
-
     fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
-        let props: &P = &self.props;
-        let inline_props = unsafe { std::mem::transmute::<&P, &P>(props) };
-
-        (self.render_fn)(Scope {
-            scope,
-            props: inline_props,
-        })
-    }
-
-    fn replace_heap(&self, new: Box<dyn Any>) {
-        // let new = new.downcast::<P>().unwrap();
-        // self.heap_props.borrow_mut().replace(new);
+        let props = unsafe { std::mem::transmute::<&P, &P>(&self.props) };
+        (self.render_fn)(Scope { scope, props })
     }
 }
 
@@ -463,7 +416,7 @@ impl<'a> NodeFactory<'a> {
     /// Directly pass in text blocks without the need to use the format_args macro.
     pub fn static_text(&self, text: &'static str) -> VNode<'a> {
         VNode::Text(self.bump.alloc(VText {
-            dom_id: empty_cell(),
+            id: empty_cell(),
             text,
             is_static: true,
         }))
@@ -492,7 +445,7 @@ impl<'a> NodeFactory<'a> {
         VNode::Text(self.bump.alloc(VText {
             text,
             is_static,
-            dom_id: empty_cell(),
+            id: empty_cell(),
         }))
     }
 
@@ -545,14 +498,14 @@ impl<'a> NodeFactory<'a> {
         let key = key.map(|f| self.raw_text(f).0);
 
         VNode::Element(self.bump.alloc(VElement {
-            tag_name,
+            tag: tag_name,
             key,
             namespace,
             listeners,
             attributes,
             children,
-            dom_id: empty_cell(),
-            parent_id: empty_cell(),
+            id: empty_cell(),
+            parent: empty_cell(),
         }))
     }
 
@@ -621,9 +574,7 @@ impl<'a> NodeFactory<'a> {
         }
 
         if nodes.is_empty() {
-            VNode::Placeholder(self.bump.alloc(VPlaceholder {
-                dom_id: empty_cell(),
-            }))
+            VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
         } else {
             VNode::Fragment(VFragment {
                 children: nodes.into_bump_slice(),
@@ -643,9 +594,7 @@ impl<'a> NodeFactory<'a> {
         }
 
         if nodes.is_empty() {
-            VNode::Placeholder(self.bump.alloc(VPlaceholder {
-                dom_id: empty_cell(),
-            }))
+            VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
         } else {
             let children = nodes.into_bump_slice();
 
@@ -686,9 +635,9 @@ impl<'a> NodeFactory<'a> {
         }
 
         if nodes.is_empty() {
-            Some(VNode::Placeholder(self.bump.alloc(VPlaceholder {
-                dom_id: empty_cell(),
-            })))
+            Some(VNode::Placeholder(
+                self.bump.alloc(VPlaceholder { id: empty_cell() }),
+            ))
         } else {
             let children = nodes.into_bump_slice();
 
@@ -766,9 +715,7 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
         match self {
             Some(lazy) => lazy.call(cx),
-            None => VNode::Placeholder(cx.bump.alloc(VPlaceholder {
-                dom_id: empty_cell(),
-            })),
+            None => VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() })),
         }
     }
 }

+ 7 - 7
packages/core/src/scopes.rs

@@ -44,11 +44,11 @@ impl ScopeArena {
         // this will *never* show up in the diffing process
         // todo: figure out why this is necessary. i forgot. whoops.
         let el = bump.alloc(VElement {
-            tag_name: "root",
+            tag: "root",
             namespace: None,
             key: None,
-            dom_id: Cell::new(Some(ElementId(0))),
-            parent_id: Default::default(),
+            id: Cell::new(Some(ElementId(0))),
+            parent: Default::default(),
             listeners: &[],
             attributes: &[],
             children: &[],
@@ -279,7 +279,7 @@ impl ScopeArena {
             let node = frame
                 .bump
                 .alloc(VNode::Placeholder(frame.bump.alloc(VPlaceholder {
-                    dom_id: Default::default(),
+                    id: Default::default(),
                 })));
             frame.node.set(unsafe { extend_vnode(node) });
         }
@@ -310,7 +310,7 @@ impl ScopeArena {
                         }
                     }
 
-                    cur_el = real_el.parent_id.get();
+                    cur_el = real_el.parent.get();
                 }
             }
         }
@@ -869,7 +869,7 @@ impl BumpFrame {
 
         let node = &*bump.alloc(VText {
             text: "placeholdertext",
-            dom_id: Default::default(),
+            id: Default::default(),
             is_static: false,
         });
         let node = bump.alloc(VNode::Text(unsafe { std::mem::transmute(node) }));
@@ -881,7 +881,7 @@ impl BumpFrame {
         self.bump.reset();
         let node = self.bump.alloc(VText {
             text: "placeholdertext",
-            dom_id: Default::default(),
+            id: Default::default(),
             is_static: false,
         });
         let node = self

+ 8 - 9
packages/core/tests/lifecycle.rs

@@ -109,14 +109,14 @@ fn components_generate() {
         *render_phase += 1;
 
         cx.render(match *render_phase {
-            0 => rsx!("Text0"),
-            1 => rsx!(div {}),
-            2 => rsx!("Text2"),
-            3 => rsx!(Child {}),
-            4 => rsx!({ None as Option<()> }),
-            5 => rsx!("text 3"),
-            6 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }),
-            7 => rsx!(Child {}),
+            1 => rsx!("Text0"),
+            2 => rsx!(div {}),
+            3 => rsx!("Text2"),
+            4 => rsx!(Child {}),
+            5 => rsx!({ None as Option<()> }),
+            6 => rsx!("text 3"),
+            7 => rsx!({ (0..2).map(|f| rsx!("text {f}")) }),
+            8 => rsx!(Child {}),
             _ => todo!(),
         })
     };
@@ -220,7 +220,6 @@ fn components_generate() {
 
 #[test]
 fn component_swap() {
-    // simple_logger::init();
     static App: Component<()> = |cx| {
         let render_phase = cx.use_hook(|_| 0, |f| f);
         *render_phase += 1;

+ 1 - 1
packages/desktop/src/lib.rs

@@ -132,7 +132,7 @@ impl DesktopController {
             runtime.block_on(async move {
                 // LocalSet::new().block_on(&runtime, async move {
                 let mut dom =
-                    VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
+                    VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
 
                 let edits = dom.rebuild();
 

+ 2 - 2
packages/ssr/src/lib.rs

@@ -168,7 +168,7 @@ impl<'a> TextRenderer<'a, '_> {
                     }
                 }
 
-                write!(f, "<{}", el.tag_name)?;
+                write!(f, "<{}", el.tag)?;
 
                 let mut inner_html = None;
                 let mut attr_iter = el.attributes.iter().peekable();
@@ -233,7 +233,7 @@ impl<'a> TextRenderer<'a, '_> {
                     }
                 }
 
-                write!(f, "</{}>", el.tag_name)?;
+                write!(f, "</{}>", el.tag)?;
                 if self.cfg.newline {
                     writeln!(f)?;
                 }