mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 01:42:14 +00:00
feat: render hover docs with converted result (#701)
* feat: render hover docs with converted result * dev: update snapshot
This commit is contained in:
parent
e35b9f9c73
commit
e57cf36f9b
10 changed files with 100 additions and 39 deletions
|
@ -482,6 +482,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
named: named_docs,
|
named: named_docs,
|
||||||
rest: rest_docs,
|
rest: rest_docs,
|
||||||
ret_ty: (),
|
ret_ty: (),
|
||||||
|
def_docs: Default::default(),
|
||||||
}))),
|
}))),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, OnceLock};
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tinymist_world::base::{EntryState, ShadowApi, TaskInputs};
|
use tinymist_world::base::{EntryState, ShadowApi, TaskInputs};
|
||||||
use tinymist_world::LspWorld;
|
use tinymist_world::LspWorld;
|
||||||
use typst::foundations::{Bytes, Value};
|
use typst::foundations::{Bytes, Func, Value};
|
||||||
use typst::{
|
use typst::{
|
||||||
diag::StrResult,
|
diag::StrResult,
|
||||||
syntax::{FileId, VirtualPath},
|
syntax::{FileId, VirtualPath},
|
||||||
|
@ -17,6 +17,7 @@ use super::tidy::*;
|
||||||
use crate::analysis::{ParamAttrs, ParamSpec};
|
use crate::analysis::{ParamAttrs, ParamSpec};
|
||||||
use crate::docs::library;
|
use crate::docs::library;
|
||||||
use crate::ty::Interned;
|
use crate::ty::Interned;
|
||||||
|
use crate::upstream::plain_docs_sentence;
|
||||||
use crate::{ty::Ty, AnalysisContext};
|
use crate::{ty::Ty, AnalysisContext};
|
||||||
|
|
||||||
type TypeRepr = Option<(/* short */ String, /* long */ String)>;
|
type TypeRepr = Option<(/* short */ String, /* long */ String)>;
|
||||||
|
@ -137,6 +138,66 @@ pub struct SignatureDocsT<T> {
|
||||||
pub rest: Option<ParamDocsT<T>>,
|
pub rest: Option<ParamDocsT<T>>,
|
||||||
/// The return type.
|
/// The return type.
|
||||||
pub ret_ty: T,
|
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.
|
/// Documentation about a signature.
|
||||||
|
@ -250,28 +311,7 @@ pub(crate) fn signature_docs(
|
||||||
runtime_fn: &Value,
|
runtime_fn: &Value,
|
||||||
mut doc_ty: Option<ShowTypeRepr>,
|
mut doc_ty: Option<ShowTypeRepr>,
|
||||||
) -> Option<SignatureDocs> {
|
) -> Option<SignatureDocs> {
|
||||||
let func = match runtime_fn {
|
let func = runtime_fn.clone().cast::<Func>().ok()?;
|
||||||
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 sig = ctx.signature_dyn(func.clone());
|
let sig = ctx.signature_dyn(func.clone());
|
||||||
let type_sig = sig.type_sig().clone();
|
let type_sig = sig.type_sig().clone();
|
||||||
|
|
||||||
|
@ -311,6 +351,7 @@ pub(crate) fn signature_docs(
|
||||||
named,
|
named,
|
||||||
rest,
|
rest,
|
||||||
ret_ty,
|
ret_ty,
|
||||||
|
def_docs: OnceLock::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,7 +383,7 @@ pub(crate) fn convert_docs(world: &LspWorld, content: &str) -> StrResult<EcoStri
|
||||||
.convert()
|
.convert()
|
||||||
.map_err(|e| eco_format!("failed to convert to markdown: {e}"))?;
|
.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> {
|
pub(crate) fn identify_docs(kind: DocStringKind, docs: EcoString) -> StrResult<SymbolDocs> {
|
||||||
|
|
|
@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/hover/annotate_fn.typ
|
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"
|
"range": "8:20:8:38"
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/hover/pagebreak.typ
|
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"
|
"range": "0:20:0:29"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
input_file: crates/tinymist-query/src/fixtures/hover/user.typ
|
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"
|
"range": "3:20:3:21"
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,14 +256,15 @@ fn def_tooltip(
|
||||||
results.push(MarkedString::LanguageString(LanguageString {
|
results.push(MarkedString::LanguageString(LanguageString {
|
||||||
language: "typc".to_owned(),
|
language: "typc".to_owned(),
|
||||||
value: format!(
|
value: format!(
|
||||||
"let {name}({params});",
|
"let {name}({params}){result};",
|
||||||
name = lnk.name,
|
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) {
|
if let Some(doc) = sig {
|
||||||
results.push(MarkedString::String(doc));
|
results.push(MarkedString::String(doc.def_docs().into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(link) = ExternalDocLink::get(ctx, &lnk) {
|
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;
|
struct ExternalDocLink;
|
||||||
|
|
||||||
impl ExternalDocLink {
|
impl ExternalDocLink {
|
||||||
|
|
|
@ -499,10 +499,8 @@ impl TypliteWorker {
|
||||||
func(Args::new(self, c.args()))
|
func(Args::new(self, c.args()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contextual(&self, node: &SyntaxNode) -> Result<Value> {
|
fn contextual(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||||
let _ = node;
|
self.render(node, false)
|
||||||
|
|
||||||
Ok(Value::None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn include(&self, node: &SyntaxNode) -> Result<Value> {
|
fn include(&self, node: &SyntaxNode) -> Result<Value> {
|
||||||
|
|
|
@ -150,6 +150,7 @@ function initClient(context: ExtensionContext, config: Record<string, any>) {
|
||||||
for (const content of hover.contents) {
|
for (const content of hover.contents) {
|
||||||
if (content instanceof vscode.MarkdownString) {
|
if (content instanceof vscode.MarkdownString) {
|
||||||
content.isTrusted = trustedCommands;
|
content.isTrusted = trustedCommands;
|
||||||
|
content.supportHtml = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hover;
|
return hover;
|
||||||
|
|
|
@ -374,7 +374,7 @@ fn e2e() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let hash = replay_log(&tinymist_binary, &root.join("neovim"));
|
let hash = replay_log(&tinymist_binary, &root.join("neovim"));
|
||||||
insta::assert_snapshot!(hash, @"siphash128_13:c6f72c26cfa3725161446bfc9a84fd89");
|
insta::assert_snapshot!(hash, @"siphash128_13:31b50bcd716fccacfd71057fd764a7bc");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -385,7 +385,7 @@ fn e2e() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let hash = replay_log(&tinymist_binary, &root.join("vscode"));
|
let hash = replay_log(&tinymist_binary, &root.join("vscode"));
|
||||||
insta::assert_snapshot!(hash, @"siphash128_13:3d7342fcd50a7d88cc19938b3d814dc4");
|
insta::assert_snapshot!(hash, @"siphash128_13:801a7b1171fad3200bbe8082f50a055f");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue