Skip to main content

j_cli/util/
md_render.rs

1/// 在终端中渲染 Markdown 文本
2/// 优先通过嵌入的 md_render 二进制渲染(效果更佳),
3/// 如果不可用则 fallback 到 termimad
4#[macro_export]
5macro_rules! md {
6    ($($arg:tt)*) => {{
7        let text = format!($($arg)*);
8        $crate::util::md_render::render_md(&text);
9    }};
10}
11
12/// 在终端中渲染单行 Markdown(不换行,用于内联场景)
13#[macro_export]
14macro_rules! md_inline {
15    ($($arg:tt)*) => {{
16        let text = format!($($arg)*);
17        termimad::print_inline(&text);
18    }};
19}
20
21/// 获取嵌入的 render 二进制路径
22/// 首次调用时释放嵌入的二进制到 ~/.jdata/bin/md_render,后续复用
23fn md_render_path() -> Option<std::path::PathBuf> {
24    // 如果不是 macOS arm64,则返回 None
25    #[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
26    {
27        return None;
28    }
29
30    #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
31    {
32        use std::os::unix::fs::PermissionsExt;
33
34        // 从统一资源模块获取二进制
35        let binary_data = crate::assets::MD_RENDER_BINARY;
36
37        let data_dir = crate::config::YamlConfig::data_dir();
38        let bin_dir = data_dir.join("bin");
39        let ask_path = bin_dir.join("md_render");
40
41        if ask_path.exists() {
42            // 已释放过,检查大小是否一致(版本更新时自动覆盖)
43            if let Ok(meta) = std::fs::metadata(&ask_path) {
44                if meta.len() == binary_data.len() as u64 {
45                    return Some(ask_path);
46                }
47            }
48        }
49
50        // 首次释放或版本更新,写入嵌入的二进制
51        if std::fs::create_dir_all(&bin_dir).is_err() {
52            return None;
53        }
54        if std::fs::write(&ask_path, binary_data).is_err() {
55            return None;
56        }
57        // 设置可执行权限 (chmod 755)
58        if let Ok(meta) = std::fs::metadata(&ask_path) {
59            let mut perms = meta.permissions();
60            perms.set_mode(0o755);
61            let _ = std::fs::set_permissions(&ask_path, perms);
62        }
63
64        Some(ask_path)
65    }
66}
67
68/// 渲染 Markdown 文本到终端
69/// 优先通过嵌入的 ask 二进制渲染(stdin → stdout,效果更佳),
70/// 如果不可用则 fallback 到 termimad
71pub fn render_md(text: &str) {
72    use std::io::Write;
73    use std::process::{Command, Stdio};
74
75    // 获取嵌入的 render 二进制路径
76    let renderer_path = md_render_path();
77
78    if let Some(path) = renderer_path {
79        // 调用 render:直接从 stdin 读取 Markdown,渲染后输出 stdout
80        let result = Command::new(&path)
81            .stdin(Stdio::piped())
82            .stdout(Stdio::inherit())
83            .stderr(Stdio::inherit())
84            .spawn();
85
86        match result {
87            Ok(mut child) => {
88                if let Some(mut stdin) = child.stdin.take() {
89                    let _ = stdin.write_all(text.as_bytes());
90                    drop(stdin);
91                }
92                let _ = child.wait();
93                return;
94            }
95            Err(_) => {}
96        }
97    }
98
99    // fallback 到 termimad
100    termimad::print_text(text);
101}