azalea_chat/
text_component.rs1use std::fmt::Display;
2
3use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer};
4
5use crate::{base_component::BaseComponent, style::ChatFormatting, FormattedText};
6
7#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
9pub struct TextComponent {
10 pub base: BaseComponent,
11 pub text: String,
12}
13
14impl Serialize for TextComponent {
15 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
16 where
17 S: Serializer,
18 {
19 let mut state = serializer.serialize_map(None)?;
20 state.serialize_entry("text", &self.text)?;
21 Serialize::serialize(&self.base, FlatMapSerializer(&mut state))?;
22 if !self.base.siblings.is_empty() {
23 state.serialize_entry("extra", &self.base.siblings)?;
24 }
25 state.end()
26 }
27}
28
29#[cfg(feature = "simdnbt")]
30impl simdnbt::Serialize for TextComponent {
31 fn to_compound(self) -> simdnbt::owned::NbtCompound {
32 let mut compound = simdnbt::owned::NbtCompound::new();
33 compound.insert("text", self.text);
34 compound.extend(self.base.style.to_compound());
35 if !self.base.siblings.is_empty() {
36 compound.insert(
37 "extra",
38 simdnbt::owned::NbtList::from(
39 self.base
40 .siblings
41 .into_iter()
42 .map(|component| component.to_compound())
43 .collect::<Vec<_>>(),
44 ),
45 );
46 }
47 compound
48 }
49}
50
51const LEGACY_FORMATTING_CODE_SYMBOL: char = '§';
52
53pub fn legacy_color_code_to_text_component(legacy_color_code: &str) -> TextComponent {
57 let mut components: Vec<TextComponent> = Vec::with_capacity(1);
58 let mut i = 0;
65 while i < legacy_color_code.chars().count() {
66 if legacy_color_code.chars().nth(i).unwrap() == LEGACY_FORMATTING_CODE_SYMBOL {
67 let formatting_code = legacy_color_code.chars().nth(i + 1);
68 let Some(formatting_code) = formatting_code else {
69 i += 1;
70 continue;
71 };
72 if let Some(formatter) = ChatFormatting::from_code(formatting_code) {
73 if components.is_empty() || !components.last().unwrap().text.is_empty() {
74 components.push(TextComponent::new("".to_string()));
75 }
76
77 let style = &mut components.last_mut().unwrap().base.style;
78 style.apply_formatting(&formatter);
80 }
81 i += 1;
82 } else {
83 if components.is_empty() {
84 components.push(TextComponent::new("".to_string()));
85 }
86 components
87 .last_mut()
88 .unwrap()
89 .text
90 .push(legacy_color_code.chars().nth(i).unwrap());
91 };
92 i += 1;
93 }
94
95 if components.is_empty() {
96 return TextComponent::new("".to_string());
97 }
98
99 let mut final_component = components.remove(0);
102 for component in components {
103 final_component.base.siblings.push(component.get());
104 }
105
106 final_component
107}
108
109impl TextComponent {
110 pub fn new(text: String) -> Self {
111 if text.contains(LEGACY_FORMATTING_CODE_SYMBOL) {
113 legacy_color_code_to_text_component(&text)
114 } else {
115 Self {
116 base: BaseComponent::new(),
117 text,
118 }
119 }
120 }
121
122 fn get(self) -> FormattedText {
123 FormattedText::Text(self)
124 }
125}
126
127impl Display for TextComponent {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 for component in FormattedText::Text(self.clone()).into_iter() {
131 let component_text = match &component {
132 FormattedText::Text(c) => c.text.to_string(),
133 FormattedText::Translatable(c) => c.read()?.to_string(),
134 };
135
136 f.write_str(&component_text)?;
137 }
138
139 Ok(())
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::style::Ansi;
147
148 #[test]
149 fn test_hypixel_motd() {
150 let component =
151 TextComponent::new("§aHypixel Network §c[1.8-1.18]\n§b§lHAPPY HOLIDAYS".to_string())
152 .get();
153 assert_eq!(
154 component.to_ansi(),
155 format!(
156 "{GREEN}Hypixel Network {RED}[1.8-1.18]\n{BOLD}{AQUA}HAPPY HOLIDAYS{RESET}",
157 GREEN = Ansi::rgb(ChatFormatting::Green.color().unwrap()),
158 RED = Ansi::rgb(ChatFormatting::Red.color().unwrap()),
159 AQUA = Ansi::rgb(ChatFormatting::Aqua.color().unwrap()),
160 BOLD = Ansi::BOLD,
161 RESET = Ansi::RESET
162 )
163 );
164 }
165
166 #[test]
167 fn test_legacy_color_code_to_component() {
168 let component = TextComponent::new("§lHello §r§1w§2o§3r§4l§5d".to_string()).get();
169 assert_eq!(
170 component.to_ansi(),
171 format!(
172 "{BOLD}Hello {RESET}{DARK_BLUE}w{DARK_GREEN}o{DARK_AQUA}r{DARK_RED}l{DARK_PURPLE}d{RESET}",
173 BOLD = Ansi::BOLD,
174 RESET = Ansi::RESET,
175 DARK_BLUE = Ansi::rgb(ChatFormatting::DarkBlue.color().unwrap()),
176 DARK_GREEN = Ansi::rgb(ChatFormatting::DarkGreen.color().unwrap()),
177 DARK_AQUA = Ansi::rgb(ChatFormatting::DarkAqua.color().unwrap()),
178 DARK_RED = Ansi::rgb(ChatFormatting::DarkRed.color().unwrap()),
179 DARK_PURPLE = Ansi::rgb(ChatFormatting::DarkPurple.color().unwrap())
180 )
181 );
182 }
183}