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