Skip to main content

osp_cli/ui/
inline.rs

1use crate::ui::document::{LineBlock, LinePart};
2use crate::ui::style::{StyleOverrides, StyleToken, apply_style_with_theme_overrides};
3use crate::ui::theme::ThemeDefinition;
4
5pub fn parts_from_inline(text: &str) -> Vec<LinePart> {
6    let mut parts: Vec<LinePart> = Vec::new();
7    let mut buf = String::new();
8    let chars: Vec<char> = text.chars().collect();
9    let mut i = 0usize;
10
11    let flush = |parts: &mut Vec<LinePart>, buf: &mut String| {
12        if !buf.is_empty() {
13            parts.push(LinePart {
14                text: buf.clone(),
15                token: None,
16            });
17            buf.clear();
18        }
19    };
20
21    while i < chars.len() {
22        let ch = chars[i];
23        if ch == '\\' && i + 1 < chars.len() {
24            buf.push(chars[i + 1]);
25            i += 2;
26            continue;
27        }
28
29        if ch == '`' {
30            let fence = if i + 1 < chars.len() && chars[i + 1] == '`' {
31                2
32            } else {
33                1
34            };
35            let mut end = i + fence;
36            while end + fence - 1 < chars.len() {
37                if chars[end..end + fence].iter().all(|c| *c == '`') {
38                    flush(&mut parts, &mut buf);
39                    let content: String = chars[i + fence..end].iter().collect();
40                    parts.push(LinePart {
41                        text: content,
42                        token: Some(StyleToken::Key),
43                    });
44                    i = end + fence;
45                    break;
46                }
47                end += 1;
48            }
49            if end + fence - 1 < chars.len() {
50                continue;
51            }
52        }
53
54        if ch == '*' && i + 1 < chars.len() && chars[i + 1] == '*' {
55            let mut end = i + 2;
56            while end + 1 < chars.len() {
57                if chars[end] == '*' && chars[end + 1] == '*' {
58                    flush(&mut parts, &mut buf);
59                    let content: String = chars[i + 2..end].iter().collect();
60                    parts.push(LinePart {
61                        text: content,
62                        token: Some(StyleToken::PanelBorder),
63                    });
64                    i = end + 2;
65                    break;
66                }
67                end += 1;
68            }
69            if end + 1 < chars.len() {
70                continue;
71            }
72        }
73
74        if ch == '*' {
75            let mut end = i + 1;
76            while end < chars.len() {
77                if chars[end] == '*' {
78                    flush(&mut parts, &mut buf);
79                    let content: String = chars[i + 1..end].iter().collect();
80                    parts.push(LinePart {
81                        text: content,
82                        token: Some(StyleToken::Muted),
83                    });
84                    i = end + 1;
85                    break;
86                }
87                end += 1;
88            }
89            if end < chars.len() {
90                continue;
91            }
92        }
93
94        buf.push(ch);
95        i += 1;
96    }
97
98    flush(&mut parts, &mut buf);
99    parts
100}
101
102pub fn line_from_inline(text: &str) -> LineBlock {
103    LineBlock {
104        parts: parts_from_inline(text),
105    }
106}
107
108pub fn render_inline(
109    text: &str,
110    color: bool,
111    theme: &ThemeDefinition,
112    overrides: &StyleOverrides,
113) -> String {
114    let mut out = String::new();
115    for part in parts_from_inline(text) {
116        if let Some(token) = part.token {
117            out.push_str(&apply_style_with_theme_overrides(
118                &part.text, token, color, theme, overrides,
119            ));
120        } else {
121            out.push_str(&part.text);
122        }
123    }
124    out
125}