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
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(Copy, Clone)]
pub enum SupSub {
    Sup,
    Sub,
}

#[derive(Copy, Clone)]
pub enum StTy {
    Str,
    Tty,
}

pub trait Render {
    /// uniform treatment `Question` detail to markdown String
    ///
    /// * `_with_env`: whether display 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).unwrap();
            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).unwrap();
            None
        },
    }
}

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

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