damascene_core/widgets/code_block.rs
1//! Code block — surface-tinted monospace block for fenced code.
2//!
3//! Mirrors HTML's `<pre><code>` shape: a bordered, rounded surface that
4//! wraps a multi-line monospace body. The body uses the `mono` font path
5//! at `TEXT_SM` size and does not wrap — long lines extend horizontally
6//! rather than reflowing, the same convention shadcn's typography uses
7//! for fenced code (`overflow-x-auto`). Author wraps the result in
8//! `scroll([...])` for horizontal scroll if desired; that integration is
9//! out of scope for this primitive.
10//!
11//! Two entry points: [`code_block`] takes a verbatim string and renders
12//! it as one mono text leaf, while [`code_block_chrome`] takes any
13//! pre-built body `El` (typically a styled [`text_runs`] paragraph
14//! produced by a syntax highlighter) and applies the same surface
15//! chrome — used by `damascene-markdown` to render highlighted fenced code
16//! without duplicating the chrome tokens.
17//!
18//! ```ignore
19//! use damascene_core::prelude::*;
20//!
21//! code_block("fn main() {\n println!(\"hi\");\n}")
22//! ```
23
24// Lock in full per-item documentation for this module (issue #73).
25#![warn(missing_docs)]
26
27use std::panic::Location;
28
29use crate::style::StyleProfile;
30use crate::tokens;
31use crate::tree::*;
32use crate::widgets::text::text;
33
34/// Fenced-code surface (HTML `<pre><code>`) — the verbatim string as a
35/// non-wrapping mono leaf inside the standard code-block chrome. Long
36/// lines extend horizontally; wrap in `scroll([...])` if needed.
37#[track_caller]
38pub fn code_block(s: impl Into<String>) -> El {
39 let loc = Location::caller();
40 let body = text(s)
41 .at_loc(loc)
42 .mono()
43 .font_size(tokens::TEXT_SM.size)
44 .nowrap_text()
45 .width(Size::Hug)
46 .height(Size::Hug);
47 code_block_chrome_at(body, loc)
48}
49
50/// Wrap an author-supplied body `El` in the standard code-block surface
51/// chrome (sunken muted fill, themed border, rounded corners,
52/// `SPACE_3` padding). Used by syntax-highlighter integrations that
53/// need the same chrome around a styled `text_runs([...])` paragraph
54/// rather than a plain mono text leaf.
55#[track_caller]
56pub fn code_block_chrome(body: El) -> El {
57 code_block_chrome_at(body, Location::caller())
58}
59
60fn code_block_chrome_at(body: El, loc: &'static Location<'static>) -> El {
61 column([body])
62 .at_loc(loc)
63 .style_profile(StyleProfile::Surface)
64 .surface_role(SurfaceRole::Sunken)
65 .fill(tokens::MUTED)
66 .stroke(tokens::BORDER)
67 .default_radius(tokens::RADIUS_MD)
68 .default_padding(Sides::all(tokens::SPACE_3))
69 .width(Size::Fill(1.0))
70 .height(Size::Hug)
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn code_block_wraps_mono_body_in_sunken_surface() {
79 let block = code_block("fn main() {\n println!(\"hi\");\n}");
80
81 assert_eq!(block.kind, Kind::Group);
82 assert_eq!(block.axis, Axis::Column);
83 assert_eq!(block.style_profile, StyleProfile::Surface);
84 assert_eq!(block.surface_role, SurfaceRole::Sunken);
85 assert_eq!(block.fill, Some(tokens::MUTED));
86 assert_eq!(block.stroke, Some(tokens::BORDER));
87 assert_eq!(block.padding, Sides::all(tokens::SPACE_3));
88 assert_eq!(block.width, Size::Fill(1.0));
89 assert_eq!(block.children.len(), 1);
90
91 let body = &block.children[0];
92 assert_eq!(body.kind, Kind::Text);
93 assert!(body.font_mono);
94 assert_eq!(body.font_size, tokens::TEXT_SM.size);
95 assert_eq!(body.text_wrap, TextWrap::NoWrap);
96 assert_eq!(
97 body.text.as_deref(),
98 Some("fn main() {\n println!(\"hi\");\n}")
99 );
100 }
101}