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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
pub mod qs_detail;
pub mod run_res;
pub mod submit_list;

use std::{
    env,
    io::{stdout, Read, Seek},
};

use pulldown_cmark::{Options, Parser};
use pulldown_cmark_mdcat::{
    push_tty, resources::FileResourceHandler, Environment, Settings, TerminalProgram, TerminalSize,
    Theme,
};
use ratatui::text::Line;
use regex::{Captures, Regex};
use syntect::parsing::SyntaxSet;

#[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 paragraph
    fn to_tui_vec(&self) -> Vec<Line>;
    /// render to terminal
    fn render_to_terminal(&self) {
        let set = Settings {
            terminal_capabilities: TerminalProgram::detect().capabilities(),
            terminal_size:         TerminalSize::detect().unwrap_or_default(),
            syntax_set:            &SyntaxSet::load_defaults_newlines(),
            theme:                 Theme::default(),
        };

        rendering(&set, &self.to_md_str(false), StTy::Tty);
    }

    /// Get a rendered markdown String
    ///
    /// * `with_env`: whether display Compile Environment
    /// * `col`: width
    /// * `row`: height
    fn to_rendered_str(&self, with_env: bool, col: u16, row: u16) -> String {
        let term_size = TerminalSize {
            columns: col,
            rows: row,
            ..Default::default()
        };
        let set = Settings {
            terminal_capabilities: TerminalProgram::detect().capabilities(),
            terminal_size:         term_size,
            syntax_set:            &SyntaxSet::load_defaults_newlines(),
            theme:                 Theme::default(),
        };

        rendering(&set, &self.to_md_str(with_env), StTy::Str).expect("rendering error")
    }
}

/// uniform render
///
/// * `set`: `Settings`
/// * `md_str`: String
/// * `target`: to terminal(return `None`) or rendered string(return `Some(String)`)
pub fn rendering(set: &Settings, md_str: &str, target: StTy) -> Option<String> {
    let pwd = env::current_dir().ok()?;
    let env = Environment::for_local_directory(&pwd).ok()?;
    let handle = FileResourceHandler::new(104_857_600);

    let parser = Parser::new_ext(md_str, Options::all());

    match target {
        StTy::Str => {
            // rendr to `out`
            let mut out = std::io::Cursor::new(vec![]);
            push_tty(set, &env, &handle, &mut out, parser).expect("render to str failed");
            out.rewind().ok()?;

            let mut temp = String::new();
            out.read_to_string(&mut temp).ok()?;
            Some(temp)
        },
        StTy::Tty => {
            // rendr to terminal
            push_tty(set, &env, &handle, &mut stdout(), parser).expect("render to tty failed");
            None
        },
    }
}

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>()
        },
    }
}