Enum Component

Source
pub enum Component {
    String(String),
    Array(Vec<Component>),
    Object(Box<ComponentObject>),
}
Expand description

Represents a Minecraft text component. Allows de/serialization using Serde with JSON.

Variants§

§

String(String)

Simple string component (shorthand for {text: "value"})

§

Array(Vec<Component>)

Array of components (shorthand for first component with extras)

§

Object(Box<ComponentObject>)

Full component object with properties

Implementations§

Source§

impl Component

Source

pub fn text(text: impl AsRef<str>) -> Self

Creates a plain text component

Examples found in repository?
src/lib.rs (line 567)
566    pub fn append_newline(self) -> Self {
567        self.append(Component::text("\n"))
568    }
569
570    /// Appends a space character
571    pub fn append_space(self) -> Self {
572        self.append(Component::text(" "))
573    }
More examples
Hide additional examples
src/minimessage.rs (line 136)
124    fn parse_text(&mut self) -> Result<(), MiniMessageError> {
125        let start = self.position;
126        while self.position < self.input.len() {
127            if self.starts_with('<') || (self.config.parse_legacy_colors && self.starts_with('&')) {
128                break;
129            }
130            self.position += 1;
131        }
132
133        if start < self.position {
134            let text = &self.input[start..self.position];
135            let current_style = self.current_style();
136            let mut comp = Component::text(text);
137            comp = comp.color(current_style.color.clone());
138            comp = comp.decorations(&self.collect_decorations());
139            self.component_parts.push(comp);
140        }
141        Ok(())
142    }
143
144    fn parse_tag(&mut self) -> Result<(), MiniMessageError> {
145        // Skip '<'
146        self.position += 1;
147
148        if self.starts_with('/') {
149            // Closing tag
150            self.position += 1;
151            let tag_name = self.read_tag_name()?;
152            self.handle_close_tag(&tag_name)?;
153            self.expect('>')?;
154        } else {
155            // Opening tag
156            let tag_name = self.read_tag_name()?;
157            let mut args = Vec::new();
158            let mut self_closing = false;
159
160            while self.position < self.input.len() {
161                // skip whitespace
162                self.skip_whitespace();
163                // skip colon separators
164                while self.starts_with(':') {
165                    self.position += 1;
166                    self.skip_whitespace();
167                }
168
169                // if we’ve hit the end of the tag, stop
170                if self.starts_with('>') || self.starts_with('/') {
171                    break;
172                }
173
174                // read an argument
175                let arg = self.read_argument()?;
176                args.push(arg);
177            }
178
179            // Check for self-closing tag
180            if self.starts_with('/') {
181                self.position += 1;
182                self_closing = true;
183            }
184            self.expect('>')?;
185
186            self.handle_open_tag(&tag_name, args, self_closing)?;
187        }
188
189        Ok(())
190    }
191
192    fn read_tag_name(&mut self) -> Result<String, MiniMessageError> {
193        let start = self.position;
194        while self.position < self.input.len() {
195            let c = self.current_char();
196            if !c.is_ascii_alphanumeric() && c != '_' && c != '-' {
197                break;
198            }
199            self.position += 1;
200        }
201        if start == self.position {
202            return Err(MiniMessageError("Expected tag name".to_string()));
203        }
204        Ok(self.input[start..self.position].to_lowercase())
205    }
206
207    fn read_argument(&mut self) -> Result<String, MiniMessageError> {
208        if self.starts_with('\'') || self.starts_with('"') {
209            self.read_quoted_string()
210        } else {
211            self.read_unquoted_string()
212        }
213    }
214
215    fn read_quoted_string(&mut self) -> Result<String, MiniMessageError> {
216        let quote_char = self.current_char();
217        self.position += 1;
218
219        let mut escaped = false;
220        let mut result = String::new();
221
222        while self.position < self.input.len() {
223            let c = self.current_char();
224            if escaped {
225                result.push(c);
226                escaped = false;
227            } else if c == '\\' {
228                escaped = true;
229            } else if c == quote_char {
230                self.position += 1;
231                return Ok(result);
232            } else {
233                result.push(c);
234            }
235            self.position += 1;
236        }
237
238        Err(MiniMessageError("Unterminated quoted string".to_string()))
239    }
240
241    fn read_unquoted_string(&mut self) -> Result<String, MiniMessageError> {
242        let start = self.position;
243        while self.position < self.input.len() {
244            let c = self.current_char();
245            if c == ':' || c == '>' || c == '/' || c.is_whitespace() {
246                break;
247            }
248            self.position += 1;
249        }
250        // what?
251        // if start == self.position {
252        //     return Err(MiniMessageError("Expected argument".to_string()));
253        // }
254        Ok(self.input[start..self.position].to_string())
255    }
256
257    fn handle_open_tag(
258        &mut self,
259        tag: &str,
260        args: Vec<String>,
261        self_closing: bool,
262    ) -> Result<(), MiniMessageError> {
263        match tag {
264            // Colors
265            "black" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Black)))?,
266            "dark_blue" => {
267                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkBlue)))?
268            }
269            "dark_green" => {
270                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkGreen)))?
271            }
272            "dark_aqua" => {
273                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkAqua)))?
274            }
275            "dark_red" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkRed)))?,
276            "dark_purple" => {
277                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkPurple)))?
278            }
279            "gold" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Gold)))?,
280            "gray" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Gray)))?,
281            "dark_gray" => {
282                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkGray)))?
283            }
284            "blue" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Blue)))?,
285            "green" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Green)))?,
286            "aqua" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Aqua)))?,
287            "red" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Red)))?,
288            "light_purple" => {
289                self.push_style(|s| s.color = Some(Color::Named(NamedColor::LightPurple)))?
290            }
291            "yellow" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Yellow)))?,
292            "white" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::White)))?,
293            "color" | "colour" | "c" if !args.is_empty() => {
294                if let Some(color) = args[0].parse::<Color>().ok() {
295                    self.push_style(|s| s.color = Some(color))?
296                }
297            }
298
299            // Decorations
300            "bold" | "b" => self.push_style(|s| s.bold = Some(true))?,
301            "italic" | "i" | "em" => self.push_style(|s| s.italic = Some(true))?,
302            "underlined" | "u" => self.push_style(|s| s.underlined = Some(true))?,
303            "strikethrough" | "st" => self.push_style(|s| s.strikethrough = Some(true))?,
304            "obfuscated" | "obf" => self.push_style(|s| s.obfuscated = Some(true))?,
305
306            // Reset tag
307            "reset" => self.reset_style()?,
308
309            // Click events
310            "click" if args.len() >= 2 => {
311                let action = args[0].as_str();
312                let value = args[1].as_str();
313                match action {
314                    "run_command" => self.push_style(|s| {
315                        s.click_event = Some(ClickEvent::RunCommand {
316                            command: value.to_string(),
317                        })
318                    })?,
319                    "suggest_command" => self.push_style(|s| {
320                        s.click_event = Some(ClickEvent::SuggestCommand {
321                            command: value.to_string(),
322                        })
323                    })?,
324                    "open_url" => self.push_style(|s| {
325                        s.click_event = Some(ClickEvent::OpenUrl {
326                            url: value.to_string(),
327                        })
328                    })?,
329                    "copy_to_clipboard" => self.push_style(|s| {
330                        s.click_event = Some(ClickEvent::CopyToClipboard {
331                            value: value.to_string(),
332                        })
333                    })?,
334                    _ => {}
335                }
336            }
337
338            // Hover events
339            "hover" if !args.is_empty() => {
340                let action = args[0].as_str();
341                if action == "show_text" && args.len() >= 2 {
342                    let mut nested_parser = Parser::new(&args[1], self.config);
343                    let nested = nested_parser.parse()?;
344                    self.push_style(|s| {
345                        s.hover_event = Some(HoverEvent::ShowText { value: nested })
346                    })?;
347                }
348            }
349
350            // Newline
351            "newline" | "br" => {
352                self.component_parts.push(Component::text("\n"));
353            }
354
355            // Insertion
356            "insert" | "insertion" if !args.is_empty() => {
357                self.push_style(|s| s.insertion = Some(args[0].clone()))?
358            }
359
360            // Handle self-closing tags
361            _ if self_closing => {
362                // For self-closing tags, create an empty component with the style
363                let current_style = self.current_style();
364                let mut comp = Component::text("");
365                comp = comp.color(current_style.color.clone());
366                comp = comp.decorations(&self.collect_decorations());
367                self.component_parts.push(comp);
368            }
369
370            // Unknown tags are treated as text
371            _ => {
372                let mut tag_text = format!("<{tag}");
373                for arg in args {
374                    tag_text.push(':');
375                    tag_text.push_str(&arg);
376                }
377                if self_closing {
378                    tag_text.push('/');
379                }
380                tag_text.push('>');
381                self.component_parts
382                    .push(Component::text(tag_text).apply_fallback_style(self.current_style()));
383            }
384        }
385
386        Ok(())
387    }
Source

pub fn append<C: Into<Component>>(self, component: C) -> Self

Appends a child component

Examples found in repository?
src/lib.rs (line 567)
566    pub fn append_newline(self) -> Self {
567        self.append(Component::text("\n"))
568    }
569
570    /// Appends a space character
571    pub fn append_space(self) -> Self {
572        self.append(Component::text(" "))
573    }
Source

pub fn append_newline(self) -> Self

Appends a newline character

Source

pub fn append_space(self) -> Self

Appends a space character

Source

pub fn to_plain_text(&self) -> Cow<'_, str>

Returns the “plain text” representation of this component as a Cow<str>.

This is the closest equivalent to Kyori’s plain text serializer

§Behavior by variant
  • Component::String: Returns a borrowed &str of the string content.
  • Component::Object:
    • If the object has text and no extra children, returns a borrowed &str.
    • If the object has extra children, returns an owned String concatenating the object’s text (if any) with the to_plain_text of each child.
  • Component::Array: Returns an owned String concatenating the to_plain_text of each element in the array.
§Notes

This method may allocate a new String if concatenation is needed.
Use Self::get_plain_text if you only need a cheap, O(1) borrowed string from a single component.

Examples found in repository?
src/lib.rs (line 609)
593    pub fn to_plain_text(&self) -> Cow<'_, str> {
594        match self {
595            Component::String(s) => Cow::Borrowed(s),
596            Component::Object(obj) => {
597                if obj.extra.is_none()
598                    && let Some(text) = &obj.text
599                {
600                    return Cow::Borrowed(text);
601                }
602
603                let mut result = String::new();
604                if let Some(text) = &obj.text {
605                    result.push_str(text);
606                }
607                if let Some(children) = &obj.extra {
608                    for child in children {
609                        result.push_str(&child.to_plain_text());
610                    }
611                }
612                Cow::Owned(result)
613            }
614            Component::Array(components) => {
615                let mut result = String::new();
616                for c in components {
617                    result.push_str(&c.to_plain_text());
618                }
619                Cow::Owned(result)
620            }
621        }
622    }
Source

pub fn get_plain_text(&self) -> Option<&str>

Returns the raw text field if this component is a string or an object with a text field.

Does not traverse children or consider other fields. Cheap, O(1) operation alternative to Self::to_plain_text.

Source

pub fn apply_fallback_style(self, fallback: &Style) -> Self

Applies fallback styles to unset properties

Examples found in repository?
src/lib.rs (line 649)
636    pub fn apply_fallback_style(self, fallback: &Style) -> Self {
637        match self {
638            Component::String(s) => {
639                let mut obj = ComponentObject {
640                    content_type: Some(ContentType::Text),
641                    text: Some(s),
642                    ..Default::default()
643                };
644                obj.merge_style(fallback);
645                Component::Object(Box::new(obj))
646            }
647            Component::Array(vec) => Component::Array(
648                vec.into_iter()
649                    .map(|c| c.apply_fallback_style(fallback))
650                    .collect(),
651            ),
652            Component::Object(mut obj) => {
653                obj.merge_style(fallback);
654                if let Some(extras) = obj.extra {
655                    obj.extra = Some(
656                        extras
657                            .into_iter()
658                            .map(|c| c.apply_fallback_style(fallback))
659                            .collect(),
660                    );
661                }
662                Component::Object(obj)
663            }
664        }
665    }
More examples
Hide additional examples
src/minimessage.rs (line 382)
257    fn handle_open_tag(
258        &mut self,
259        tag: &str,
260        args: Vec<String>,
261        self_closing: bool,
262    ) -> Result<(), MiniMessageError> {
263        match tag {
264            // Colors
265            "black" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Black)))?,
266            "dark_blue" => {
267                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkBlue)))?
268            }
269            "dark_green" => {
270                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkGreen)))?
271            }
272            "dark_aqua" => {
273                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkAqua)))?
274            }
275            "dark_red" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkRed)))?,
276            "dark_purple" => {
277                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkPurple)))?
278            }
279            "gold" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Gold)))?,
280            "gray" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Gray)))?,
281            "dark_gray" => {
282                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkGray)))?
283            }
284            "blue" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Blue)))?,
285            "green" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Green)))?,
286            "aqua" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Aqua)))?,
287            "red" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Red)))?,
288            "light_purple" => {
289                self.push_style(|s| s.color = Some(Color::Named(NamedColor::LightPurple)))?
290            }
291            "yellow" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Yellow)))?,
292            "white" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::White)))?,
293            "color" | "colour" | "c" if !args.is_empty() => {
294                if let Some(color) = args[0].parse::<Color>().ok() {
295                    self.push_style(|s| s.color = Some(color))?
296                }
297            }
298
299            // Decorations
300            "bold" | "b" => self.push_style(|s| s.bold = Some(true))?,
301            "italic" | "i" | "em" => self.push_style(|s| s.italic = Some(true))?,
302            "underlined" | "u" => self.push_style(|s| s.underlined = Some(true))?,
303            "strikethrough" | "st" => self.push_style(|s| s.strikethrough = Some(true))?,
304            "obfuscated" | "obf" => self.push_style(|s| s.obfuscated = Some(true))?,
305
306            // Reset tag
307            "reset" => self.reset_style()?,
308
309            // Click events
310            "click" if args.len() >= 2 => {
311                let action = args[0].as_str();
312                let value = args[1].as_str();
313                match action {
314                    "run_command" => self.push_style(|s| {
315                        s.click_event = Some(ClickEvent::RunCommand {
316                            command: value.to_string(),
317                        })
318                    })?,
319                    "suggest_command" => self.push_style(|s| {
320                        s.click_event = Some(ClickEvent::SuggestCommand {
321                            command: value.to_string(),
322                        })
323                    })?,
324                    "open_url" => self.push_style(|s| {
325                        s.click_event = Some(ClickEvent::OpenUrl {
326                            url: value.to_string(),
327                        })
328                    })?,
329                    "copy_to_clipboard" => self.push_style(|s| {
330                        s.click_event = Some(ClickEvent::CopyToClipboard {
331                            value: value.to_string(),
332                        })
333                    })?,
334                    _ => {}
335                }
336            }
337
338            // Hover events
339            "hover" if !args.is_empty() => {
340                let action = args[0].as_str();
341                if action == "show_text" && args.len() >= 2 {
342                    let mut nested_parser = Parser::new(&args[1], self.config);
343                    let nested = nested_parser.parse()?;
344                    self.push_style(|s| {
345                        s.hover_event = Some(HoverEvent::ShowText { value: nested })
346                    })?;
347                }
348            }
349
350            // Newline
351            "newline" | "br" => {
352                self.component_parts.push(Component::text("\n"));
353            }
354
355            // Insertion
356            "insert" | "insertion" if !args.is_empty() => {
357                self.push_style(|s| s.insertion = Some(args[0].clone()))?
358            }
359
360            // Handle self-closing tags
361            _ if self_closing => {
362                // For self-closing tags, create an empty component with the style
363                let current_style = self.current_style();
364                let mut comp = Component::text("");
365                comp = comp.color(current_style.color.clone());
366                comp = comp.decorations(&self.collect_decorations());
367                self.component_parts.push(comp);
368            }
369
370            // Unknown tags are treated as text
371            _ => {
372                let mut tag_text = format!("<{tag}");
373                for arg in args {
374                    tag_text.push(':');
375                    tag_text.push_str(&arg);
376                }
377                if self_closing {
378                    tag_text.push('/');
379                }
380                tag_text.push('>');
381                self.component_parts
382                    .push(Component::text(tag_text).apply_fallback_style(self.current_style()));
383            }
384        }
385
386        Ok(())
387    }
Source

pub fn color(self, color: Option<Color>) -> Self

Sets text color

Examples found in repository?
src/minimessage.rs (line 137)
124    fn parse_text(&mut self) -> Result<(), MiniMessageError> {
125        let start = self.position;
126        while self.position < self.input.len() {
127            if self.starts_with('<') || (self.config.parse_legacy_colors && self.starts_with('&')) {
128                break;
129            }
130            self.position += 1;
131        }
132
133        if start < self.position {
134            let text = &self.input[start..self.position];
135            let current_style = self.current_style();
136            let mut comp = Component::text(text);
137            comp = comp.color(current_style.color.clone());
138            comp = comp.decorations(&self.collect_decorations());
139            self.component_parts.push(comp);
140        }
141        Ok(())
142    }
143
144    fn parse_tag(&mut self) -> Result<(), MiniMessageError> {
145        // Skip '<'
146        self.position += 1;
147
148        if self.starts_with('/') {
149            // Closing tag
150            self.position += 1;
151            let tag_name = self.read_tag_name()?;
152            self.handle_close_tag(&tag_name)?;
153            self.expect('>')?;
154        } else {
155            // Opening tag
156            let tag_name = self.read_tag_name()?;
157            let mut args = Vec::new();
158            let mut self_closing = false;
159
160            while self.position < self.input.len() {
161                // skip whitespace
162                self.skip_whitespace();
163                // skip colon separators
164                while self.starts_with(':') {
165                    self.position += 1;
166                    self.skip_whitespace();
167                }
168
169                // if we’ve hit the end of the tag, stop
170                if self.starts_with('>') || self.starts_with('/') {
171                    break;
172                }
173
174                // read an argument
175                let arg = self.read_argument()?;
176                args.push(arg);
177            }
178
179            // Check for self-closing tag
180            if self.starts_with('/') {
181                self.position += 1;
182                self_closing = true;
183            }
184            self.expect('>')?;
185
186            self.handle_open_tag(&tag_name, args, self_closing)?;
187        }
188
189        Ok(())
190    }
191
192    fn read_tag_name(&mut self) -> Result<String, MiniMessageError> {
193        let start = self.position;
194        while self.position < self.input.len() {
195            let c = self.current_char();
196            if !c.is_ascii_alphanumeric() && c != '_' && c != '-' {
197                break;
198            }
199            self.position += 1;
200        }
201        if start == self.position {
202            return Err(MiniMessageError("Expected tag name".to_string()));
203        }
204        Ok(self.input[start..self.position].to_lowercase())
205    }
206
207    fn read_argument(&mut self) -> Result<String, MiniMessageError> {
208        if self.starts_with('\'') || self.starts_with('"') {
209            self.read_quoted_string()
210        } else {
211            self.read_unquoted_string()
212        }
213    }
214
215    fn read_quoted_string(&mut self) -> Result<String, MiniMessageError> {
216        let quote_char = self.current_char();
217        self.position += 1;
218
219        let mut escaped = false;
220        let mut result = String::new();
221
222        while self.position < self.input.len() {
223            let c = self.current_char();
224            if escaped {
225                result.push(c);
226                escaped = false;
227            } else if c == '\\' {
228                escaped = true;
229            } else if c == quote_char {
230                self.position += 1;
231                return Ok(result);
232            } else {
233                result.push(c);
234            }
235            self.position += 1;
236        }
237
238        Err(MiniMessageError("Unterminated quoted string".to_string()))
239    }
240
241    fn read_unquoted_string(&mut self) -> Result<String, MiniMessageError> {
242        let start = self.position;
243        while self.position < self.input.len() {
244            let c = self.current_char();
245            if c == ':' || c == '>' || c == '/' || c.is_whitespace() {
246                break;
247            }
248            self.position += 1;
249        }
250        // what?
251        // if start == self.position {
252        //     return Err(MiniMessageError("Expected argument".to_string()));
253        // }
254        Ok(self.input[start..self.position].to_string())
255    }
256
257    fn handle_open_tag(
258        &mut self,
259        tag: &str,
260        args: Vec<String>,
261        self_closing: bool,
262    ) -> Result<(), MiniMessageError> {
263        match tag {
264            // Colors
265            "black" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Black)))?,
266            "dark_blue" => {
267                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkBlue)))?
268            }
269            "dark_green" => {
270                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkGreen)))?
271            }
272            "dark_aqua" => {
273                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkAqua)))?
274            }
275            "dark_red" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkRed)))?,
276            "dark_purple" => {
277                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkPurple)))?
278            }
279            "gold" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Gold)))?,
280            "gray" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Gray)))?,
281            "dark_gray" => {
282                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkGray)))?
283            }
284            "blue" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Blue)))?,
285            "green" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Green)))?,
286            "aqua" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Aqua)))?,
287            "red" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Red)))?,
288            "light_purple" => {
289                self.push_style(|s| s.color = Some(Color::Named(NamedColor::LightPurple)))?
290            }
291            "yellow" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Yellow)))?,
292            "white" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::White)))?,
293            "color" | "colour" | "c" if !args.is_empty() => {
294                if let Some(color) = args[0].parse::<Color>().ok() {
295                    self.push_style(|s| s.color = Some(color))?
296                }
297            }
298
299            // Decorations
300            "bold" | "b" => self.push_style(|s| s.bold = Some(true))?,
301            "italic" | "i" | "em" => self.push_style(|s| s.italic = Some(true))?,
302            "underlined" | "u" => self.push_style(|s| s.underlined = Some(true))?,
303            "strikethrough" | "st" => self.push_style(|s| s.strikethrough = Some(true))?,
304            "obfuscated" | "obf" => self.push_style(|s| s.obfuscated = Some(true))?,
305
306            // Reset tag
307            "reset" => self.reset_style()?,
308
309            // Click events
310            "click" if args.len() >= 2 => {
311                let action = args[0].as_str();
312                let value = args[1].as_str();
313                match action {
314                    "run_command" => self.push_style(|s| {
315                        s.click_event = Some(ClickEvent::RunCommand {
316                            command: value.to_string(),
317                        })
318                    })?,
319                    "suggest_command" => self.push_style(|s| {
320                        s.click_event = Some(ClickEvent::SuggestCommand {
321                            command: value.to_string(),
322                        })
323                    })?,
324                    "open_url" => self.push_style(|s| {
325                        s.click_event = Some(ClickEvent::OpenUrl {
326                            url: value.to_string(),
327                        })
328                    })?,
329                    "copy_to_clipboard" => self.push_style(|s| {
330                        s.click_event = Some(ClickEvent::CopyToClipboard {
331                            value: value.to_string(),
332                        })
333                    })?,
334                    _ => {}
335                }
336            }
337
338            // Hover events
339            "hover" if !args.is_empty() => {
340                let action = args[0].as_str();
341                if action == "show_text" && args.len() >= 2 {
342                    let mut nested_parser = Parser::new(&args[1], self.config);
343                    let nested = nested_parser.parse()?;
344                    self.push_style(|s| {
345                        s.hover_event = Some(HoverEvent::ShowText { value: nested })
346                    })?;
347                }
348            }
349
350            // Newline
351            "newline" | "br" => {
352                self.component_parts.push(Component::text("\n"));
353            }
354
355            // Insertion
356            "insert" | "insertion" if !args.is_empty() => {
357                self.push_style(|s| s.insertion = Some(args[0].clone()))?
358            }
359
360            // Handle self-closing tags
361            _ if self_closing => {
362                // For self-closing tags, create an empty component with the style
363                let current_style = self.current_style();
364                let mut comp = Component::text("");
365                comp = comp.color(current_style.color.clone());
366                comp = comp.decorations(&self.collect_decorations());
367                self.component_parts.push(comp);
368            }
369
370            // Unknown tags are treated as text
371            _ => {
372                let mut tag_text = format!("<{tag}");
373                for arg in args {
374                    tag_text.push(':');
375                    tag_text.push_str(&arg);
376                }
377                if self_closing {
378                    tag_text.push('/');
379                }
380                tag_text.push('>');
381                self.component_parts
382                    .push(Component::text(tag_text).apply_fallback_style(self.current_style()));
383            }
384        }
385
386        Ok(())
387    }
Source

pub fn font(self, font: Option<String>) -> Self

Sets font

Source

pub fn decoration(self, decoration: TextDecoration, state: Option<bool>) -> Self

Sets text decoration state

Source

pub fn decorations( self, decorations: &HashMap<TextDecoration, Option<bool>>, ) -> Self

Sets multiple decorations at once

Examples found in repository?
src/minimessage.rs (line 138)
124    fn parse_text(&mut self) -> Result<(), MiniMessageError> {
125        let start = self.position;
126        while self.position < self.input.len() {
127            if self.starts_with('<') || (self.config.parse_legacy_colors && self.starts_with('&')) {
128                break;
129            }
130            self.position += 1;
131        }
132
133        if start < self.position {
134            let text = &self.input[start..self.position];
135            let current_style = self.current_style();
136            let mut comp = Component::text(text);
137            comp = comp.color(current_style.color.clone());
138            comp = comp.decorations(&self.collect_decorations());
139            self.component_parts.push(comp);
140        }
141        Ok(())
142    }
143
144    fn parse_tag(&mut self) -> Result<(), MiniMessageError> {
145        // Skip '<'
146        self.position += 1;
147
148        if self.starts_with('/') {
149            // Closing tag
150            self.position += 1;
151            let tag_name = self.read_tag_name()?;
152            self.handle_close_tag(&tag_name)?;
153            self.expect('>')?;
154        } else {
155            // Opening tag
156            let tag_name = self.read_tag_name()?;
157            let mut args = Vec::new();
158            let mut self_closing = false;
159
160            while self.position < self.input.len() {
161                // skip whitespace
162                self.skip_whitespace();
163                // skip colon separators
164                while self.starts_with(':') {
165                    self.position += 1;
166                    self.skip_whitespace();
167                }
168
169                // if we’ve hit the end of the tag, stop
170                if self.starts_with('>') || self.starts_with('/') {
171                    break;
172                }
173
174                // read an argument
175                let arg = self.read_argument()?;
176                args.push(arg);
177            }
178
179            // Check for self-closing tag
180            if self.starts_with('/') {
181                self.position += 1;
182                self_closing = true;
183            }
184            self.expect('>')?;
185
186            self.handle_open_tag(&tag_name, args, self_closing)?;
187        }
188
189        Ok(())
190    }
191
192    fn read_tag_name(&mut self) -> Result<String, MiniMessageError> {
193        let start = self.position;
194        while self.position < self.input.len() {
195            let c = self.current_char();
196            if !c.is_ascii_alphanumeric() && c != '_' && c != '-' {
197                break;
198            }
199            self.position += 1;
200        }
201        if start == self.position {
202            return Err(MiniMessageError("Expected tag name".to_string()));
203        }
204        Ok(self.input[start..self.position].to_lowercase())
205    }
206
207    fn read_argument(&mut self) -> Result<String, MiniMessageError> {
208        if self.starts_with('\'') || self.starts_with('"') {
209            self.read_quoted_string()
210        } else {
211            self.read_unquoted_string()
212        }
213    }
214
215    fn read_quoted_string(&mut self) -> Result<String, MiniMessageError> {
216        let quote_char = self.current_char();
217        self.position += 1;
218
219        let mut escaped = false;
220        let mut result = String::new();
221
222        while self.position < self.input.len() {
223            let c = self.current_char();
224            if escaped {
225                result.push(c);
226                escaped = false;
227            } else if c == '\\' {
228                escaped = true;
229            } else if c == quote_char {
230                self.position += 1;
231                return Ok(result);
232            } else {
233                result.push(c);
234            }
235            self.position += 1;
236        }
237
238        Err(MiniMessageError("Unterminated quoted string".to_string()))
239    }
240
241    fn read_unquoted_string(&mut self) -> Result<String, MiniMessageError> {
242        let start = self.position;
243        while self.position < self.input.len() {
244            let c = self.current_char();
245            if c == ':' || c == '>' || c == '/' || c.is_whitespace() {
246                break;
247            }
248            self.position += 1;
249        }
250        // what?
251        // if start == self.position {
252        //     return Err(MiniMessageError("Expected argument".to_string()));
253        // }
254        Ok(self.input[start..self.position].to_string())
255    }
256
257    fn handle_open_tag(
258        &mut self,
259        tag: &str,
260        args: Vec<String>,
261        self_closing: bool,
262    ) -> Result<(), MiniMessageError> {
263        match tag {
264            // Colors
265            "black" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Black)))?,
266            "dark_blue" => {
267                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkBlue)))?
268            }
269            "dark_green" => {
270                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkGreen)))?
271            }
272            "dark_aqua" => {
273                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkAqua)))?
274            }
275            "dark_red" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkRed)))?,
276            "dark_purple" => {
277                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkPurple)))?
278            }
279            "gold" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Gold)))?,
280            "gray" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Gray)))?,
281            "dark_gray" => {
282                self.push_style(|s| s.color = Some(Color::Named(NamedColor::DarkGray)))?
283            }
284            "blue" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Blue)))?,
285            "green" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Green)))?,
286            "aqua" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Aqua)))?,
287            "red" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Red)))?,
288            "light_purple" => {
289                self.push_style(|s| s.color = Some(Color::Named(NamedColor::LightPurple)))?
290            }
291            "yellow" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::Yellow)))?,
292            "white" => self.push_style(|s| s.color = Some(Color::Named(NamedColor::White)))?,
293            "color" | "colour" | "c" if !args.is_empty() => {
294                if let Some(color) = args[0].parse::<Color>().ok() {
295                    self.push_style(|s| s.color = Some(color))?
296                }
297            }
298
299            // Decorations
300            "bold" | "b" => self.push_style(|s| s.bold = Some(true))?,
301            "italic" | "i" | "em" => self.push_style(|s| s.italic = Some(true))?,
302            "underlined" | "u" => self.push_style(|s| s.underlined = Some(true))?,
303            "strikethrough" | "st" => self.push_style(|s| s.strikethrough = Some(true))?,
304            "obfuscated" | "obf" => self.push_style(|s| s.obfuscated = Some(true))?,
305
306            // Reset tag
307            "reset" => self.reset_style()?,
308
309            // Click events
310            "click" if args.len() >= 2 => {
311                let action = args[0].as_str();
312                let value = args[1].as_str();
313                match action {
314                    "run_command" => self.push_style(|s| {
315                        s.click_event = Some(ClickEvent::RunCommand {
316                            command: value.to_string(),
317                        })
318                    })?,
319                    "suggest_command" => self.push_style(|s| {
320                        s.click_event = Some(ClickEvent::SuggestCommand {
321                            command: value.to_string(),
322                        })
323                    })?,
324                    "open_url" => self.push_style(|s| {
325                        s.click_event = Some(ClickEvent::OpenUrl {
326                            url: value.to_string(),
327                        })
328                    })?,
329                    "copy_to_clipboard" => self.push_style(|s| {
330                        s.click_event = Some(ClickEvent::CopyToClipboard {
331                            value: value.to_string(),
332                        })
333                    })?,
334                    _ => {}
335                }
336            }
337
338            // Hover events
339            "hover" if !args.is_empty() => {
340                let action = args[0].as_str();
341                if action == "show_text" && args.len() >= 2 {
342                    let mut nested_parser = Parser::new(&args[1], self.config);
343                    let nested = nested_parser.parse()?;
344                    self.push_style(|s| {
345                        s.hover_event = Some(HoverEvent::ShowText { value: nested })
346                    })?;
347                }
348            }
349
350            // Newline
351            "newline" | "br" => {
352                self.component_parts.push(Component::text("\n"));
353            }
354
355            // Insertion
356            "insert" | "insertion" if !args.is_empty() => {
357                self.push_style(|s| s.insertion = Some(args[0].clone()))?
358            }
359
360            // Handle self-closing tags
361            _ if self_closing => {
362                // For self-closing tags, create an empty component with the style
363                let current_style = self.current_style();
364                let mut comp = Component::text("");
365                comp = comp.color(current_style.color.clone());
366                comp = comp.decorations(&self.collect_decorations());
367                self.component_parts.push(comp);
368            }
369
370            // Unknown tags are treated as text
371            _ => {
372                let mut tag_text = format!("<{tag}");
373                for arg in args {
374                    tag_text.push(':');
375                    tag_text.push_str(&arg);
376                }
377                if self_closing {
378                    tag_text.push('/');
379                }
380                tag_text.push('>');
381                self.component_parts
382                    .push(Component::text(tag_text).apply_fallback_style(self.current_style()));
383            }
384        }
385
386        Ok(())
387    }
Source

pub fn click_event(self, event: Option<ClickEvent>) -> Self

Sets click event

Source

pub fn hover_event(self, event: Option<HoverEvent>) -> Self

Sets hover event

Source

pub fn insertion(self, insertion: Option<String>) -> Self

Sets insertion text

Source

pub fn has_decoration(&self, decoration: TextDecoration) -> bool

Checks if a decoration is enabled

Source

pub fn has_styling(&self) -> bool

Checks if any styling is present

Source

pub fn set_children(self, children: Vec<Component>) -> Self

Sets child components

Source

pub fn get_children(&self) -> &[Component]

Gets child components

Trait Implementations§

Source§

impl Clone for Component

Source§

fn clone(&self) -> Component

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Component

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Component

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl<T: AsRef<str>> From<T> for Component

Source§

fn from(value: T) -> Component

Converts to this type from the input type.
Source§

impl PartialEq for Component

Source§

fn eq(&self, other: &Component) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Serialize for Component

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for Component

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,