1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
pub mod qs_detail;
pub mod run_res;
pub mod submit_list;

use std::{
    io::prelude::Write,
    process::{Command, Stdio},
};

#[cfg(feature = "ratatui")]
use ratatui::text::Line;
use regex::{Captures, Regex};

#[derive(Clone, Copy)]
#[derive(Debug)]
#[derive(Default)]
#[derive(PartialEq, Eq)]
pub enum SupSub {
    #[default]
    Sup,
    Sub,
}

#[derive(Clone, Copy)]
#[derive(Debug)]
#[derive(Default)]
#[derive(PartialEq, Eq)]
pub enum StTy {
    Str,
    #[default]
    Tty,
}

pub trait Render {
    /// uniform treatment `Question` detail to markdown String
    ///
    /// * `_with_env`: for `Question` whether display `Question` Compile Environment
    fn to_md_str(&self, _with_env: bool) -> String {
        String::new()
    }

    /// for ratatui's paragraph widget
    #[cfg(feature = "ratatui")]
    fn to_tui_vec(&self) -> Vec<Line>;

    /// use [`mdcat`](https://github.com/swsnr/mdcat/) render question content
    fn render_with_mdcat(&self) {
        let content = self.to_md_str(false);
        'out: {
            let Ok(mut child) = Command::new("mdcat")
                .stdin(Stdio::piped())
                .stdout(Stdio::inherit())
                .spawn()
            else {
                break 'out;
            };
            if let Some(mut stdin) = child.stdin.take() {
                if stdin
                    .write_all(content.as_bytes())
                    .is_err()
                {
                    break 'out;
                };
                // stdin drop here
            }
            else {
                break 'out;
            };

            let Ok(exit_status) = child.wait()
            else {
                break 'out;
            };
            if exit_status.success() {
                return;
            }
        }

        println!("{content}");
    }
}

pub fn to_sub_sup_script(content: &str) -> String {
    let sup_re = Regex::new("<sup>(?P<num>[0-9]*)</sup>").expect("regex new failed");
    let sub_re = Regex::new("<sub>(?P<num>[0-9]*)</sub>").expect("regex new failed");

    let content = sup_re.replace_all(content, |cap: &Captures| {
        let num = cap["num"].parse().unwrap_or_default();
        superscript(num, SupSub::Sup)
    });

    let content = sub_re.replace_all(&content, |cap: &Captures| {
        let num = cap["num"].parse().unwrap_or_default();
        superscript(num, SupSub::Sub)
    });

    content.to_string()
}

const SUPER_NUM: [char; 10] = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
const SUB_NUM: [char; 10] = ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'];

pub fn superscript(n: usize, sub_or_sup: SupSub) -> String {
    let sub_or_sup = match sub_or_sup {
        SupSub::Sup => SUPER_NUM,
        SupSub::Sub => SUB_NUM,
    };
    match n {
        0..=9 => sub_or_sup[n].to_string(),
        mut num => {
            // 2 is enough, avoid frequently create string
            let mut res = String::with_capacity(2);
            while num > 0 {
                res.push(sub_or_sup[num % 10]);
                num /= 10;
            }
            res.chars().rev().collect::<String>()
        },
    }
}