feat: render hover docs with converted result (#701)

* feat: render hover docs with converted result

* dev: update snapshot
This commit is contained in:
Myriad-Dreamin 2024-10-17 19:18:36 +08:00 committed by GitHub
parent e35b9f9c73
commit e57cf36f9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 100 additions and 39 deletions

View file

@ -482,6 +482,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
named: named_docs,
rest: rest_docs,
ret_ty: (),
def_docs: Default::default(),
}))),
);

View file

@ -1,13 +1,13 @@
use core::fmt;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::{Arc, OnceLock};
use ecow::{eco_format, EcoString};
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use tinymist_world::base::{EntryState, ShadowApi, TaskInputs};
use tinymist_world::LspWorld;
use typst::foundations::{Bytes, Value};
use typst::foundations::{Bytes, Func, Value};
use typst::{
diag::StrResult,
syntax::{FileId, VirtualPath},
@ -17,6 +17,7 @@ use super::tidy::*;
use crate::analysis::{ParamAttrs, ParamSpec};
use crate::docs::library;
use crate::ty::Interned;
use crate::upstream::plain_docs_sentence;
use crate::{ty::Ty, AnalysisContext};
type TypeRepr = Option<(/* short */ String, /* long */ String)>;
@ -137,6 +138,66 @@ pub struct SignatureDocsT<T> {
pub rest: Option<ParamDocsT<T>>,
/// The return type.
pub ret_ty: T,
/// The full documentation for the signature.
#[serde(skip)]
pub def_docs: OnceLock<String>,
}
impl SignatureDocsT<TypeRepr> {
/// Get full documentation for the signature.
pub fn def_docs(&self) -> &String {
self.def_docs
.get_or_init(|| plain_docs_sentence(&format!("{}", DefDocs(self))).into())
}
}
struct DefDocs<'a>(&'a SignatureDocs);
impl fmt::Display for DefDocs<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let docs = self.0;
let base_docs = docs.docs.trim();
let has_params_docs = !docs.pos.is_empty() || !docs.named.is_empty() || docs.rest.is_some();
if !base_docs.is_empty() {
f.write_str(base_docs)?;
if has_params_docs {
f.write_str("\n\n")?;
}
}
if has_params_docs {
f.write_str("## Parameters")?;
for p in &docs.pos {
write!(f, "\n\n@positional `{}`", p.name)?;
if !p.docs.is_empty() {
f.write_str("")?;
f.write_str(&p.docs)?;
}
}
for (name, p) in &docs.named {
write!(f, "\n\n@named `{name}`")?;
if !p.docs.is_empty() {
f.write_str("")?;
f.write_str(&p.docs)?;
}
}
if let Some(rest) = &docs.rest {
write!(f, "\n\n@rest `{}`", rest.name)?;
if !rest.docs.is_empty() {
f.write_str("")?;
f.write_str(&rest.docs)?;
}
}
}
Ok(())
}
}
/// Documentation about a signature.
@ -250,28 +311,7 @@ pub(crate) fn signature_docs(
runtime_fn: &Value,
mut doc_ty: Option<ShowTypeRepr>,
) -> Option<SignatureDocs> {
let func = match runtime_fn {
Value::Func(f) => f,
_ => return None,
};
// todo: documenting with bindings
use typst::foundations::func::Repr;
let mut func = func;
loop {
match func.inner() {
Repr::Element(..) | Repr::Native(..) => {
break;
}
Repr::With(w) => {
func = &w.0;
}
Repr::Closure(..) => {
break;
}
}
}
let func = runtime_fn.clone().cast::<Func>().ok()?;
let sig = ctx.signature_dyn(func.clone());
let type_sig = sig.type_sig().clone();
@ -311,6 +351,7 @@ pub(crate) fn signature_docs(
named,
rest,
ret_ty,
def_docs: OnceLock::new(),
})
}
@ -342,7 +383,7 @@ pub(crate) fn convert_docs(world: &LspWorld, content: &str) -> StrResult<EcoStri
.convert()
.map_err(|e| eco_format!("failed to convert to markdown: {e}"))?;
Ok(conv)
Ok(conv.replace("```example", "```typ"))
}
pub(crate) fn identify_docs(kind: DocStringKind, docs: EcoString) -> StrResult<SymbolDocs> {

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/annotate_fn.typ
---
{
"contents": "```typc\nlet touying-fn-wrapper(fn: (..: []) => any | function, ..args: arguments, max-repetitions: int | none = none, repetitions: int | none = none);\n```\n\n---\n\n\n #let fn = `(..fn-args) => any`;\n\n - fn (function, fn): The `fn`.\n - max-repetitions (int): The `max-repetitions`.\n - repetitions (int): The `repetitions`.\n - args (any, fn-args): The `args`.",
"contents": "```typc\nlet touying-fn-wrapper(fn: (..: []) => any | function, ..args: arguments, max-repetitions: int | none = none, repetitions: int | none = none) = none;\n```\n\n---\n## Parameters\n\n@positional `fn` — The `fn`.\n\n@named `max-repetitions` — The `max-repetitions`.\n\n@named `repetitions` — The `repetitions`.\n\n@rest `args` — The `args`.",
"range": "8:20:8:38"
}

File diff suppressed because one or more lines are too long

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/pagebreak.typ
---
{
"contents": "```typc\nlet pagebreak(to: \"even\" | \"odd\" | none = none, weak: bool = false);\n```\n\n---\n\n\nA manual page break.\n\nMust not be used inside any containers.\n\n# Example\n```typ\nThe next page contains\nmore details on compound theory.\n#pagebreak()\n\n== Compound Theory\nIn 1984, the first ...\n```\n\n---\n[Open docs](https://typst.app/docs/reference/layout/pagebreak/)",
"contents": "```typc\nlet pagebreak(to: \"even\" | \"odd\" | none = none, weak: bool = false);\n```\n\n---\nA manual page break.\n\nMust not be used inside any containers.\n\n# Example\n```typ\nThe next page contains\nmore details on compound theory.\n#pagebreak()\n\n== Compound Theory\nIn 1984, the first ...\n```\n\n## Parameters\n\n@named `to` — If given, ensures that the next page will be an even/odd page, with an\nempty page in between if necessary.\n\n```typ\n#set page(height: 30pt)\n\nFirst.\n#pagebreak(to: \"odd\")\nThird.\n```\n\n@named `weak` — If `true`, the page break is skipped if the current page is already\nempty.\n\n---\n[Open docs](https://typst.app/docs/reference/layout/pagebreak/)",
"range": "0:20:0:29"
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/user.typ
---
{
"contents": "```typc\nlet f();\n```\n\n---\n\n\nTest",
"contents": "```typc\nlet f() = 1;\n```\n\n---\nTest",
"range": "3:20:3:21"
}

View file

@ -256,14 +256,15 @@ fn def_tooltip(
results.push(MarkedString::LanguageString(LanguageString {
language: "typc".to_owned(),
value: format!(
"let {name}({params});",
"let {name}({params}){result};",
name = lnk.name,
params = ParamTooltip(sig.as_ref())
params = ParamTooltip(sig.as_ref()),
result = ResultTooltip(&lnk.name, sig.as_ref())
),
}));
if let Some(doc) = DocTooltip::get(ctx, &lnk) {
results.push(MarkedString::String(doc));
if let Some(doc) = sig {
results.push(MarkedString::String(doc.def_docs().into()));
}
if let Some(link) = ExternalDocLink::get(ctx, &lnk) {
@ -360,6 +361,25 @@ impl fmt::Display for ParamTooltip<'_> {
}
}
struct ResultTooltip<'a>(&'a str, Option<&'a SignatureDocs>);
impl fmt::Display for ResultTooltip<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Some(sig) = self.1 else {
return Ok(());
};
if let Some((short, _)) = &sig.ret_ty {
if short == self.0 {
return Ok(());
}
write!(f, " = {short}")
} else {
Ok(())
}
}
}
struct ExternalDocLink;
impl ExternalDocLink {