Skip to main content

build

Function build 

Source
pub fn build(nodes: &[FlatNode], source: &str) -> SourceMap
Expand description

Style the source of a parsed document.

nodes is the whole arena as twig::Editor::nodes returns it, over the source it was parsed from — the spans do the work, and the text is read only to tell a delimiter from the whitespace around it (see [fill_markup]).

The walk starts at the Kind::Doc root and goes depth-first, so a node is always painted before the children that overwrite parts of it. It uses an explicit stack rather than recursion: nesting depth is the document’s, and a thousand nested block quotes should slow a repaint down, not end it.

Examples found in repository?
examples/bench.rs (line 68)
34fn main() {
35    for kb in [10usize, 100, 1000] {
36        let src = body(kb * 1024);
37        println!("=== {} KB ===", src.len() / 1024);
38
39        let mut ed = Editor::new_str(&src, Format::Markdown).unwrap();
40        let nodes = ed.nodes().unwrap();
41        let map = wysiwyg::build(&nodes, &src, None, false, &HashMap::new(), None);
42        println!("  ({} AST nodes, {} map rows)", nodes.len(), map.rows.len());
43
44        println!("  -- per edit (unavoidable today) --");
45        time("twig edit_range (reparse)", 5, || {
46            ed.edit_range(src.len() / 2, src.len() / 2, "x").is_ok()
47        });
48        // That loop left five `x`s in the editor, and every block below measures
49        // `ed` against `src` — spans from a document five bytes longer than the
50        // string they index. Re-parse so the two are the same document again.
51        //
52        // Untimed on purpose: this is the bench putting its fixture back, not a
53        // cost leaf pays. Skipping it used to end every run in a slice panic
54        // (`push_escaped_text`, walking a span past the end of a shorter source)
55        // and would otherwise have quietly measured a build over a mismatch.
56        let mut ed = Editor::new_str(&src, Format::Markdown).unwrap();
57        time("twig nodes() FFI marshal", 5, || ed.nodes().unwrap().len());
58        time("wysiwyg::build", 5, || {
59            wysiwyg::build(&nodes, &src, None, false, &HashMap::new(), None)
60                .rows
61                .len()
62        });
63        // The source view's whole per-edit cost, next to the WYSIWYG view's, so
64        // the "cheaper view" claim in `source::build`'s docs is a measured one.
65        // It has no incremental path: this plus the marshal above is what a
66        // keystroke in `View::Source` pays.
67        time("source::build", 5, || {
68            source::build(&nodes, &src).runs().len()
69        });
70        {
71            // The incremental path with a warm cache and nothing changed: the
72            // floor cost the block cache adds even on a pure repaint — hash every
73            // block, clone every reused row, recollect stops. No subtree is
74            // marshalled (every block hits). The real keystroke win shows up in
75            // "Doc::insert + rebuild" below, which re-marshals only the edited
76            // block and reuses the rest.
77            let mut cache = wysiwyg::BlockCache::default();
78            let top = ed.child_spans(None).unwrap();
79            let _ = wysiwyg::build_cached(
80                &top,
81                &src,
82                None,
83                false,
84                &HashMap::new(),
85                None,
86                &mut cache,
87                |id| ed.subtree(twig::NodeId(id)).unwrap_or_default(),
88            );
89            time("wysiwyg::build_cached (all reused)", 5, || {
90                let top = ed.child_spans(None).unwrap();
91                wysiwyg::build_cached(
92                    &top,
93                    &src,
94                    None,
95                    false,
96                    &HashMap::new(),
97                    None,
98                    &mut cache,
99                    |id| ed.subtree(twig::NodeId(id)).unwrap_or_default(),
100                )
101                .rows
102                .len()
103            });
104        }
105
106        println!("  -- claimed hot, actually noise --");
107        time("twig source_str() (full copy)", 5, || {
108            ed.source_str().unwrap().len()
109        });
110        let clean = src.clone();
111        time("dirty compare (full cmp)", 5, || src == clean);
112
113        println!("  -- what the GUI adds on a cache miss --");
114        time("clone every row's glyphs", 5, || {
115            map.rows
116                .iter()
117                .map(|r| r.glyphs.clone())
118                .collect::<Vec<_>>()
119                .len()
120        });
121        time("hash every glyph (cache key?)", 5, || {
122            let mut n = 0u64;
123            for r in &map.rows {
124                let mut h = std::collections::hash_map::DefaultHasher::new();
125                for g in &r.glyphs {
126                    g.ch.hash(&mut h);
127                }
128                n ^= h.finish();
129            }
130            n
131        });
132
133        println!("  -- the whole path, as a frontend calls it --");
134        let mut p = std::env::temp_dir();
135        p.push(format!("leaf_bench_{kb}.md"));
136        std::fs::write(&p, &src).unwrap();
137        let mut d = Doc::open(p).unwrap();
138        d.view = View::Wysiwyg;
139        d.place_caret(src.len() / 2, false);
140        d.build_visual_unwrapped();
141        time("build_visual (cached: a repaint)", 200, || {
142            d.build_visual_unwrapped()
143        });
144        time("Doc::insert + rebuild (a keystroke)", 5, || {
145            d.insert("x");
146            d.build_visual_unwrapped();
147        });
148        println!();
149    }
150}