tinymist_query/docs/
def.rs

1use std::sync::{Arc, OnceLock};
2
3use ecow::EcoString;
4use tinymist_analysis::Signature;
5use tinymist_analysis::docs::tidy::remove_list_annotations;
6use tinymist_analysis::docs::{
7    DocText, DocTextResolver, ParamDocs, SignatureDocs, VarDocs, format_ty_short,
8};
9use tinymist_analysis::ty::DocSource;
10use typst::syntax::Span;
11
12use crate::analysis::SharedContext;
13
14pub(crate) fn var_docs(ctx: &Arc<SharedContext>, pos: Span) -> Option<VarDocs> {
15    let source = ctx.source_by_id(pos.id()?).ok()?;
16    let type_info = ctx.type_check(&source);
17    let ty = type_info.type_of_span(pos)?;
18
19    // todo multiple sources
20    // Must use raw result as type aliases contain the source information.
21    let mut srcs = ty.sources();
22    srcs.sort();
23    log::debug!("check variable docs of ty: {ty:?} => {srcs:?}");
24    let doc_source = srcs.into_iter().next()?;
25
26    // todo people can easily forget to simplify the type which is not good. we
27    // might find a way to ensure them at compile time.
28    //
29    // Must be simplified before formatting, to expand type aliases.
30    let simplified_ty = type_info.simplify(ty, false);
31    let return_ty = format_ty_short(Some(&simplified_ty));
32    match doc_source {
33        DocSource::Var(var) => {
34            let docs = type_info
35                .var_docs
36                .get(&var.def)
37                .map(|docstring| docstring.docs().clone());
38            Some(VarDocs {
39                docs: docs.unwrap_or_default(),
40                return_ty,
41                def_docs: OnceLock::new(),
42            })
43        }
44        DocSource::Ins(ins) => ins.syntax.as_ref().map(|src| {
45            let docs = convert_typst_docs_shared(ctx, src.doc.as_ref().into());
46            VarDocs {
47                docs,
48                return_ty,
49                def_docs: OnceLock::new(),
50            }
51        }),
52        _ => None,
53    }
54}
55
56pub(crate) fn sig_docs(ctx: &Arc<SharedContext>, sig: &Signature) -> Option<SignatureDocs> {
57    let type_sig = sig.type_sig().clone();
58    let mut resolver = ctx.as_ref();
59
60    let pos_in = sig
61        .primary()
62        .pos()
63        .iter()
64        .enumerate()
65        .map(|(idx, pos)| (pos, type_sig.pos(idx)));
66    let named_in = sig
67        .primary()
68        .named()
69        .iter()
70        .map(|param| (param, type_sig.named(&param.name)));
71    let rest_in = sig.primary().rest().map(|x| (x, type_sig.rest_param()));
72
73    let ret_in = type_sig.body.as_ref();
74
75    let pos = pos_in
76        .map(|(param, ty)| ParamDocs::new(&mut resolver, param, ty))
77        .collect::<Vec<_>>();
78    let named = named_in
79        .map(|(param, ty)| (param.name.clone(), ParamDocs::new(&mut resolver, param, ty)))
80        .collect::<std::collections::BTreeMap<_, _>>();
81    let rest = rest_in.map(|(param, ty)| ParamDocs::new(&mut resolver, param, ty));
82
83    let ret_ty = format_ty_short(ret_in);
84
85    let docs = sig
86        .primary()
87        .docs
88        .as_ref()
89        .map(|docs| resolve_doc_text(ctx, docs))
90        .unwrap_or_default();
91
92    Some(SignatureDocs {
93        docs,
94        pos,
95        named,
96        rest,
97        ret_ty,
98        hover_docs: OnceLock::new(),
99    })
100}
101
102pub(crate) fn resolve_doc_text(ctx: &SharedContext, docs: &DocText) -> EcoString {
103    docs.get_or_init(|raw| convert_official_doc(ctx, raw.clone()))
104        .clone()
105}
106
107impl DocTextResolver for &SharedContext {
108    fn resolve_doc_text(&mut self, docs: &DocText) -> EcoString {
109        resolve_doc_text(self, docs)
110    }
111}
112
113fn convert_official_doc(ctx: &SharedContext, docs: EcoString) -> EcoString {
114    if docs.trim().is_empty() {
115        return docs;
116    }
117
118    let docs_text = DocText::official(docs.clone());
119    match crate::docs::convert_docs(ctx, &docs_text, None) {
120        Ok(converted) => {
121            let converted = remove_list_annotations(&converted);
122            ctx.remove_html(converted.trim().into())
123        }
124        Err(err) => {
125            log::warn!("failed to convert official Typst docs to Markdown: {err}");
126            ctx.remove_html(docs)
127        }
128    }
129}
130
131pub(crate) fn convert_typst_docs_shared(ctx: &SharedContext, docs: EcoString) -> EcoString {
132    if docs.trim().is_empty() {
133        return docs;
134    }
135
136    let docs_text = DocText::plain(docs.clone());
137    match crate::docs::convert_docs(ctx, &docs_text, None) {
138        Ok(converted) => {
139            let converted = remove_list_annotations(&converted);
140            ctx.remove_html(converted.trim().into())
141        }
142        Err(err) => {
143            log::warn!("failed to convert Typst docs to Markdown: {err}");
144            ctx.remove_html(docs)
145        }
146    }
147}