tinymist_query/docs/
convert.rs

1use std::path::Path;
2use std::sync::Arc;
3
4use ecow::{EcoString, eco_format};
5use tinymist_std::path::unix_slash;
6use tinymist_world::diag::print_diagnostics_to_string;
7use tinymist_world::vfs::WorkspaceResolver;
8use tinymist_world::{
9    DiagnosticFormat, EntryReader, EntryState, ShadowApi, SourceWorld, TaskInputs,
10};
11use typlite::{Format, TypliteFeat};
12use typst::World;
13use typst::diag::StrResult;
14use typst::foundations::Bytes;
15use typst::syntax::FileId;
16
17use crate::analysis::SharedContext;
18
19pub(crate) fn convert_docs(
20    ctx: &SharedContext,
21    content: &str,
22    source_fid: Option<FileId>,
23) -> StrResult<EcoString> {
24    let mut entry = ctx.world().entry_state();
25    let import_context = source_fid.map(|fid| {
26        let root = ctx
27            .world()
28            .vfs()
29            .file_path(fid.join("/"))
30            .ok()
31            .and_then(|e| e.to_err().ok());
32        if let Some(root) = root {
33            entry = EntryState::new_workspace(root.into());
34        }
35
36        let mut imports = Vec::new();
37        if WorkspaceResolver::is_package_file(fid)
38            && let Some(pkg) = fid.package()
39        {
40            let pkg_spec = pkg.to_string();
41            imports.push(format!("#import {pkg_spec:?}"));
42            imports.push(format!("#import {pkg_spec:?}: *"));
43        }
44        imports.push(format!(
45            "#import {:?}: *",
46            unix_slash(fid.vpath().as_rooted_path())
47        ));
48        imports.join("; ")
49    });
50    let mut feat = TypliteFeat {
51        color_theme: Some(ctx.analysis.color_theme),
52        annotate_elem: true,
53        soft_error: true,
54        remove_html: ctx.analysis.remove_html,
55        import_context,
56        ..Default::default()
57    };
58
59    let entry = entry.select_in_workspace(Path::new("__tinymist_docs__.typ"));
60
61    let mut w = ctx.world().task(TaskInputs {
62        entry: Some(entry),
63        inputs: None,
64    });
65
66    // todo: bad performance: content.to_owned()
67    w.map_shadow_by_id(w.main(), Bytes::from_string(content.to_owned()))?;
68    // todo: bad performance
69    w.take_db();
70    let (w, wrap_info) = feat
71        .prepare_world(&w, Format::Md)
72        .map_err(|e| eco_format!("failed to prepare world: {e}"))?;
73
74    feat.wrap_info = wrap_info;
75
76    let w = Arc::new(w);
77    let res = typlite::Typlite::new(w.clone())
78        .with_feature(feat)
79        .convert();
80    let conv = print_diag_or_error(w.as_ref(), res)?;
81
82    Ok(conv.replace("```example", "```typ"))
83}
84
85fn print_diag_or_error<T>(
86    world: &impl SourceWorld,
87    result: tinymist_std::Result<T>,
88) -> StrResult<T> {
89    match result {
90        Ok(v) => Ok(v),
91        Err(err) => {
92            if let Some(diagnostics) = err.diagnostics() {
93                return Err(print_diagnostics_to_string(
94                    world,
95                    diagnostics.iter(),
96                    DiagnosticFormat::Human,
97                )?);
98            }
99
100            Err(eco_format!("failed to convert docs: {err}"))
101        }
102    }
103}