1use crate::Result;
2use crate::model::ErrorDiagramLayout;
3use crate::text::TextMeasurer;
4
5pub const UPSTREAM_MERMAID_VERSION: &str = merman_core::baseline::PINNED_MERMAID_BASELINE_VERSION;
6
7pub(crate) fn layout_error_diagram_typed(
8 semantic: &merman_core::diagrams::error_diagram::ErrorDiagramRenderModel,
9 _effective_config: &serde_json::Value,
10 _measurer: &dyn TextMeasurer,
11) -> Result<ErrorDiagramLayout> {
12 let lines = wrap_error_message(semantic.error_message.as_deref().unwrap_or_default());
13 let height = if lines.is_empty() {
14 512.0
15 } else {
16 500.0 + lines.len() as f64 * 56.0
17 };
18 Ok(ErrorDiagramLayout {
19 viewbox_width: 2412.0,
20 viewbox_height: height,
21 max_width_px: height,
22 })
23}
24
25pub(crate) fn wrap_error_message(message: &str) -> Vec<String> {
27 const MAX_LINE_LENGTH: usize = 75;
28 const MAX_LINES: usize = 4;
29 let mut lines = Vec::new();
30 let mut current = String::new();
31 let mut current_length = 0;
32 for token in
33 message.split(|ch: char| (ch.is_whitespace() && ch != '\u{0085}') || ch == '\u{feff}')
34 {
35 let mut chars = token.chars().peekable();
36 while chars.peek().is_some() {
37 let word: String = chars.by_ref().take(MAX_LINE_LENGTH).collect();
38 let word_length = word.chars().count();
39 let separator_length = usize::from(!current.is_empty());
40 if current_length + separator_length + word_length > MAX_LINE_LENGTH {
41 lines.push(std::mem::take(&mut current));
42 if lines.len() == MAX_LINES {
43 let last = &mut lines[MAX_LINES - 1];
44 *last = last.chars().take(MAX_LINE_LENGTH - 3).collect();
45 last.push_str("...");
46 return lines;
47 }
48 current_length = 0;
49 }
50 if !current.is_empty() {
51 current.push(' ');
52 current_length += 1;
53 }
54 current.push_str(&word);
55 current_length += word_length;
56 }
57 }
58 if !current.is_empty() {
59 lines.push(current);
60 }
61 lines
62}
63
64#[cfg(test)]
65mod tests {
66 use super::wrap_error_message;
67
68 #[test]
69 fn wraps_error_text_using_upstream_word_and_code_point_boundaries() {
70 assert!(wrap_error_message("\t \n\u{feff}").is_empty());
71 assert_eq!(wrap_error_message("A short error"), ["A short error"]);
72 assert_eq!(
73 wrap_error_message(&format!(
74 "a{}{} c{}",
75 " ".repeat(40),
76 "b".repeat(40),
77 "d".repeat(39)
78 )),
79 [
80 format!("a {}", "b".repeat(40)),
81 format!("c{}", "d".repeat(39))
82 ],
83 );
84 assert_eq!(
85 wrap_error_message(&"x".repeat(160)),
86 ["x".repeat(75), "x".repeat(75), "x".repeat(10)]
87 );
88 assert_eq!(
89 wrap_error_message(&"\u{1f600}".repeat(80)),
90 ["\u{1f600}".repeat(75), "\u{1f600}".repeat(5)]
91 );
92 assert_eq!(wrap_error_message("a\u{0085}b"), ["a\u{0085}b"]);
93 }
94
95 #[test]
96 fn error_text_is_capped_only_when_more_than_four_lines_are_needed() {
97 assert_eq!(
98 wrap_error_message(&"x".repeat(300)),
99 vec!["x".repeat(75); 4]
100 );
101 let lines = wrap_error_message(&"x".repeat(301));
102 assert_eq!(lines.len(), 4);
103 assert_eq!(lines[3], format!("{}...", "x".repeat(72)));
104 let lines = wrap_error_message(&vec!["\u{1f600}".repeat(70); 6].join(" "));
105 assert_eq!(lines.len(), 4);
106 assert_eq!(lines[3], format!("{}...", "\u{1f600}".repeat(70)));
107 }
108}