tinymist_analysis/docs/
def.rs

1use core::fmt;
2use std::collections::{BTreeMap, HashMap};
3use std::hash::{Hash, Hasher};
4use std::sync::{Arc, OnceLock};
5
6use ecow::{EcoString, eco_format};
7use serde::{Deserialize, Serialize};
8
9use super::tidy::*;
10use crate::syntax::DeclExpr;
11use crate::ty::{Interned, ParamAttrs, ParamTy, StrRef, Ty, TypeVarBounds};
12use crate::upstream::plain_docs_sentence;
13
14/// The source format of documentation text.
15#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
16pub enum DocTextKind {
17    /// Documentation that is already ready to display as Markdown.
18    Plain,
19    /// Official Typst documentation that must be converted before display.
20    Official,
21}
22
23/// Lazily resolved documentation text.
24#[derive(Debug, Clone)]
25pub struct DocText {
26    raw: EcoString,
27    kind: DocTextKind,
28    resolved: OnceLock<EcoString>,
29}
30
31impl DocText {
32    /// Creates documentation that is already ready to display as Markdown.
33    pub fn plain(raw: EcoString) -> Self {
34        Self {
35            raw,
36            kind: DocTextKind::Plain,
37            resolved: OnceLock::new(),
38        }
39    }
40
41    /// Creates official Typst documentation that must be converted before display.
42    pub fn official(raw: EcoString) -> Self {
43        Self {
44            raw,
45            kind: DocTextKind::Official,
46            resolved: OnceLock::new(),
47        }
48    }
49
50    /// Gets the raw documentation text.
51    pub fn raw(&self) -> &EcoString {
52        &self.raw
53    }
54
55    /// Gets the source format of this documentation text.
56    pub fn kind(&self) -> DocTextKind {
57        self.kind
58    }
59
60    /// Gets display-ready documentation text.
61    pub fn get_or_init(
62        &self,
63        convert_official: impl FnOnce(&EcoString) -> EcoString,
64    ) -> &EcoString {
65        match self.kind {
66            DocTextKind::Plain => &self.raw,
67            DocTextKind::Official => self.resolved.get_or_init(|| convert_official(&self.raw)),
68        }
69    }
70}
71
72impl PartialEq for DocText {
73    fn eq(&self, other: &Self) -> bool {
74        self.kind == other.kind && self.raw == other.raw
75    }
76}
77
78impl Eq for DocText {}
79
80impl PartialOrd for DocText {
81    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
82        Some(self.cmp(other))
83    }
84}
85
86impl Ord for DocText {
87    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
88        self.kind
89            .cmp(&other.kind)
90            .then_with(|| self.raw.cmp(&other.raw))
91    }
92}
93
94impl Hash for DocText {
95    fn hash<H: Hasher>(&self, state: &mut H) {
96        self.kind.hash(state);
97        self.raw.hash(state);
98    }
99}
100
101/// The documentation string of an item
102#[derive(Debug, Clone, Default)]
103pub struct DocString {
104    /// The documentation of the item
105    pub docs: Option<EcoString>,
106    /// The typing on definitions
107    pub var_bounds: HashMap<DeclExpr, TypeVarBounds>,
108    /// The variable doc associated with the item
109    pub vars: BTreeMap<StrRef, VarDoc>,
110    /// The type of the resultant type
111    pub res_ty: Option<Ty>,
112}
113
114impl DocString {
115    /// Gets the docstring as a variable doc
116    pub fn as_var(&self) -> VarDoc {
117        VarDoc {
118            docs: self.docs.clone().unwrap_or_default(),
119            ty: self.res_ty.clone(),
120        }
121    }
122
123    /// Get the documentation of a variable associated with the item
124    pub fn get_var(&self, name: &StrRef) -> Option<&VarDoc> {
125        self.vars.get(name)
126    }
127
128    /// Get the type of a variable associated with the item
129    pub fn var_ty(&self, name: &StrRef) -> Option<&Ty> {
130        self.get_var(name).and_then(|v| v.ty.as_ref())
131    }
132}
133
134/// The documentation string of a variable associated with some item.
135#[derive(Debug, Clone, Default)]
136pub struct VarDoc {
137    /// The documentation of the variable
138    pub docs: EcoString,
139    /// The type of the variable
140    pub ty: Option<Ty>,
141}
142
143impl VarDoc {
144    /// Convert the variable doc to an untyped version
145    pub fn to_untyped(&self) -> Arc<UntypedDefDocs> {
146        Arc::new(UntypedDefDocs::Variable(VarDocsT {
147            docs: self.docs.clone(),
148            return_ty: (),
149            def_docs: OnceLock::new(),
150        }))
151    }
152}
153
154type TypeRepr = Option<(
155    /* short */ EcoString,
156    /* long */ EcoString,
157    /* value */ EcoString,
158)>;
159
160/// Documentation about a definition (without type information).
161pub type UntypedDefDocs = DefDocsT<()>;
162/// Documentation about a definition.
163pub type DefDocs = DefDocsT<TypeRepr>;
164
165/// Documentation about a definition.
166#[derive(Debug, Clone, Serialize, Deserialize)]
167#[serde(tag = "kind")]
168pub enum DefDocsT<T> {
169    /// Documentation about a function.
170    #[serde(rename = "func")]
171    Function(Box<SignatureDocsT<T>>),
172    /// Documentation about a variable.
173    #[serde(rename = "var")]
174    Variable(VarDocsT<T>),
175    /// Documentation about a module.
176    #[serde(rename = "module")]
177    Module(TidyModuleDocs),
178    /// Other kinds of documentation.
179    #[serde(rename = "plain")]
180    Plain {
181        /// The content of the documentation.
182        docs: EcoString,
183    },
184}
185
186impl<T> DefDocsT<T> {
187    /// Get the markdown representation of the documentation.
188    pub fn docs(&self) -> &EcoString {
189        match self {
190            Self::Function(docs) => &docs.docs,
191            Self::Variable(docs) => &docs.docs,
192            Self::Module(docs) => &docs.docs,
193            Self::Plain { docs } => docs,
194        }
195    }
196}
197
198impl DefDocs {
199    /// Get full documentation for the signature.
200    pub fn hover_docs(&self) -> EcoString {
201        match self {
202            DefDocs::Function(docs) => docs.hover_docs().clone(),
203            _ => plain_docs_sentence(self.docs()),
204        }
205    }
206}
207
208/// Describes a primary function signature.
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct SignatureDocsT<T> {
211    /// Documentation for the function.
212    pub docs: EcoString,
213    /// The positional parameters.
214    pub pos: Vec<ParamDocsT<T>>,
215    /// The named parameters.
216    pub named: BTreeMap<Interned<str>, ParamDocsT<T>>,
217    /// The rest parameter.
218    pub rest: Option<ParamDocsT<T>>,
219    /// The return type.
220    pub ret_ty: T,
221    /// The full documentation for the signature.
222    #[serde(skip)]
223    pub hover_docs: OnceLock<EcoString>,
224}
225
226impl SignatureDocsT<TypeRepr> {
227    /// Get full documentation for the signature.
228    pub fn hover_docs(&self) -> &EcoString {
229        self.hover_docs
230            .get_or_init(|| plain_docs_sentence(&format!("{}", SigHoverDocs(self))))
231    }
232}
233
234struct SigHoverDocs<'a>(&'a SignatureDocs);
235
236impl fmt::Display for SigHoverDocs<'_> {
237    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238        let docs = self.0;
239        let base_docs = docs.docs.trim();
240
241        if !base_docs.is_empty() {
242            f.write_str(base_docs)?;
243        }
244
245        fn write_param_docs(
246            f: &mut fmt::Formatter<'_>,
247            docs: &ParamDocsT<TypeRepr>,
248            kind: &str,
249            is_first: &mut bool,
250        ) -> fmt::Result {
251            if *is_first {
252                *is_first = false;
253                write!(f, "\n\n## {}\n\n", docs.name)?;
254            } else {
255                write!(f, "\n\n## {} ({kind})\n\n", docs.name)?;
256            }
257
258            // p.cano_type.0
259            if let Some(t) = &docs.cano_type {
260                write!(f, "```typc\ntype: {}\n```\n\n", t.2)?;
261            }
262
263            f.write_str(docs.docs.trim())?;
264
265            Ok(())
266        }
267
268        if !docs.pos.is_empty() {
269            f.write_str("\n\n# Positional Parameters")?;
270
271            let mut is_first = true;
272            for pos_docs in &docs.pos {
273                write_param_docs(f, pos_docs, "positional", &mut is_first)?;
274            }
275        }
276
277        if docs.rest.is_some() {
278            f.write_str("\n\n# Rest Parameters")?;
279
280            let mut is_first = true;
281            if let Some(rest) = &docs.rest {
282                write_param_docs(f, rest, "spread right", &mut is_first)?;
283            }
284        }
285
286        if !docs.named.is_empty() {
287            f.write_str("\n\n# Named Parameters")?;
288
289            let mut is_first = true;
290            for named_docs in docs.named.values() {
291                write_param_docs(f, named_docs, "named", &mut is_first)?;
292            }
293        }
294
295        Ok(())
296    }
297}
298
299/// Documentation about a signature.
300pub type UntypedSignatureDocs = SignatureDocsT<()>;
301/// Documentation about a signature.
302pub type SignatureDocs = SignatureDocsT<TypeRepr>;
303
304impl SignatureDocs {
305    /// Get the markdown representation of the documentation.
306    pub fn print(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
307        let mut is_first = true;
308        let mut write_sep = |f: &mut dyn std::fmt::Write| {
309            if is_first {
310                is_first = false;
311                return f.write_str("\n  ");
312            }
313            f.write_str(",\n  ")
314        };
315
316        f.write_char('(')?;
317        for pos_docs in &self.pos {
318            write_sep(f)?;
319            f.write_str(&pos_docs.name)?;
320            if let Some(t) = &pos_docs.cano_type {
321                write!(f, ": {}", t.0)?;
322            }
323        }
324        if let Some(rest) = &self.rest {
325            write_sep(f)?;
326            f.write_str("..")?;
327            f.write_str(&rest.name)?;
328            if let Some(t) = &rest.cano_type {
329                write!(f, ": {}", t.0)?;
330            }
331        }
332
333        if !self.named.is_empty() {
334            let mut name_prints = vec![];
335            for v in self.named.values() {
336                let ty = v.cano_type.as_ref().map(|t| &t.0);
337                name_prints.push((v.name.clone(), ty, v.default.clone()))
338            }
339            name_prints.sort();
340            for (name, ty, val) in name_prints {
341                write_sep(f)?;
342                let val = val.as_deref().unwrap_or("any");
343                let mut default = val.trim();
344                if default.starts_with('{') && default.ends_with('}') && default.len() > 30 {
345                    default = "{ .. }"
346                }
347                if default.starts_with('`') && default.ends_with('`') && default.len() > 30 {
348                    default = "raw"
349                }
350                if default.starts_with('[') && default.ends_with(']') && default.len() > 30 {
351                    default = "content"
352                }
353                f.write_str(&name)?;
354                if let Some(ty) = ty {
355                    write!(f, ": {ty}")?;
356                }
357                if default.contains('\n') {
358                    write!(f, " = {}", default.replace("\n", "\n  "))?;
359                } else {
360                    write!(f, " = {default}")?;
361                }
362            }
363        }
364        if !is_first {
365            f.write_str(",\n")?;
366        }
367        f.write_char(')')?;
368
369        Ok(())
370    }
371}
372
373/// Documentation about a variable (without type information).
374pub type UntypedVarDocs = VarDocsT<()>;
375/// Documentation about a variable.
376pub type VarDocs = VarDocsT<Option<(EcoString, EcoString, EcoString)>>;
377
378/// Describes a primary pattern binding.
379#[derive(Debug, Clone, Serialize, Deserialize)]
380pub struct VarDocsT<T> {
381    /// Documentation for the pattern binding.
382    pub docs: EcoString,
383    /// The inferred type of the pattern binding source.
384    pub return_ty: T,
385    /// Cached documentation for the definition.
386    #[serde(skip)]
387    pub def_docs: OnceLock<String>,
388}
389
390impl VarDocs {
391    /// Get the markdown representation of the documentation.
392    pub fn def_docs(&self) -> &String {
393        self.def_docs
394            .get_or_init(|| plain_docs_sentence(&self.docs).into())
395    }
396}
397
398/// Documentation about a parameter (without type information).
399pub type TypelessParamDocs = ParamDocsT<()>;
400/// Documentation about a parameter.
401pub type ParamDocs = ParamDocsT<TypeRepr>;
402
403/// Resolves lazy documentation text.
404pub trait DocTextResolver {
405    /// Gets display-ready documentation text.
406    fn resolve_doc_text(&mut self, docs: &DocText) -> EcoString;
407}
408
409/// Describes a function parameter.
410#[derive(Debug, Clone, Serialize, Deserialize, Default)]
411pub struct ParamDocsT<T> {
412    /// The parameter's name.
413    pub name: Interned<str>,
414    /// Documentation for the parameter.
415    pub docs: EcoString,
416    /// Inferred type of the parameter.
417    pub cano_type: T,
418    /// The parameter's default name as value.
419    pub default: Option<EcoString>,
420    /// The attribute of the parameter.
421    #[serde(flatten)]
422    pub attrs: ParamAttrs,
423}
424
425impl ParamDocs {
426    /// Create a new parameter documentation.
427    pub fn new(ctx: &mut impl DocTextResolver, param: &ParamTy, ty: Option<&Ty>) -> Self {
428        let docs = param
429            .docs
430            .as_ref()
431            .map(|docs| ctx.resolve_doc_text(docs))
432            .unwrap_or_default();
433        Self {
434            name: param.name.as_ref().into(),
435            docs,
436            cano_type: format_ty(ty.or(Some(&param.ty))),
437            default: param.default.clone(),
438            attrs: param.attrs,
439        }
440    }
441}
442
443/// Formats the type.
444pub fn format_ty(ty: Option<&Ty>) -> TypeRepr {
445    let ty = ty?;
446    let short = ty.repr().unwrap_or_else(|| "any".into());
447    let long = eco_format!("{ty:?}");
448    let value = ty.value_repr().unwrap_or_else(|| "".into());
449
450    Some((short, long, value))
451}
452
453/// Formats the type when only the short display form is needed.
454pub fn format_ty_short(ty: Option<&Ty>) -> TypeRepr {
455    let ty = ty?;
456    let short = ty.repr().unwrap_or_else(|| "any".into());
457    Some((short.clone(), short.clone(), short))
458}