mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 05:05:00 +00:00
feat: provide parameter docs in hover tips (#702)
* feat: supports parameter docs * dev: update snapshot
This commit is contained in:
parent
e57cf36f9b
commit
d9d10df7a4
11 changed files with 190 additions and 95 deletions
|
@ -21,7 +21,7 @@ use crate::analysis::{
|
||||||
analyze_bib, analyze_expr_, analyze_import_, analyze_signature, post_type_check, BibInfo,
|
analyze_bib, analyze_expr_, analyze_import_, analyze_signature, post_type_check, BibInfo,
|
||||||
DefUseInfo, DocString, ImportInfo, PathPreference, Signature, SignatureTarget, Ty, TypeScheme,
|
DefUseInfo, DocString, ImportInfo, PathPreference, Signature, SignatureTarget, Ty, TypeScheme,
|
||||||
};
|
};
|
||||||
use crate::docs::{DocStringKind, SignatureDocs};
|
use crate::docs::{DocStringKind, SignatureDocs, VarDocs};
|
||||||
use crate::syntax::{
|
use crate::syntax::{
|
||||||
construct_module_dependencies, find_expr_in_import, get_deref_target, resolve_id_by_path,
|
construct_module_dependencies, find_expr_in_import, get_deref_target, resolve_id_by_path,
|
||||||
scan_workspace_files, DerefTarget, LexicalHierarchy, ModuleDependency,
|
scan_workspace_files, DerefTarget, LexicalHierarchy, ModuleDependency,
|
||||||
|
@ -573,6 +573,10 @@ impl<'w> AnalysisContext<'w> {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn variable_docs(&mut self, pos: &LinkedNode) -> Option<VarDocs> {
|
||||||
|
crate::docs::variable_docs(self, pos)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn signature_docs(&mut self, runtime_fn: &Value) -> Option<SignatureDocs> {
|
pub(crate) fn signature_docs(&mut self, runtime_fn: &Value) -> Option<SignatureDocs> {
|
||||||
crate::docs::signature_docs(self, runtime_fn, None)
|
crate::docs::signature_docs(self, runtime_fn, None)
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,9 +120,19 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
var
|
var
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_var(&mut self, s: Span, r: IdentRef) -> Option<Interned<TypeVar>> {
|
fn get_var(&mut self, root: &LinkedNode<'_>, ident: ast::Ident) -> Option<Interned<TypeVar>> {
|
||||||
|
let s = ident.span();
|
||||||
|
let r = to_ident_ref(root, ident)?;
|
||||||
let def_id = self.get_def_id(s, &r)?;
|
let def_id = self.get_def_id(s, &r)?;
|
||||||
|
self.get_var_by_id(s, r.name.as_ref().into(), def_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_var_by_id(
|
||||||
|
&mut self,
|
||||||
|
s: Span,
|
||||||
|
name: Interned<str>,
|
||||||
|
def_id: DefId,
|
||||||
|
) -> Option<Interned<TypeVar>> {
|
||||||
// todo: false positive of clippy
|
// todo: false positive of clippy
|
||||||
#[allow(clippy::map_entry)]
|
#[allow(clippy::map_entry)]
|
||||||
if !self.info.vars.contains_key(&def_id) {
|
if !self.info.vars.contains_key(&def_id) {
|
||||||
|
@ -130,13 +140,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
let init_expr = self.init_var(def);
|
let init_expr = self.init_var(def);
|
||||||
self.info.vars.insert(
|
self.info.vars.insert(
|
||||||
def_id,
|
def_id,
|
||||||
TypeVarBounds::new(
|
TypeVarBounds::new(TypeVar { name, def: def_id }, init_expr),
|
||||||
TypeVar {
|
|
||||||
name: r.name.as_str().into(),
|
|
||||||
def: def_id,
|
|
||||||
},
|
|
||||||
init_expr,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use reflexo::TakeAs;
|
use reflexo::TakeAs;
|
||||||
use typst::foundations::{IntoValue, Module, Str, Type};
|
use typst::foundations::{IntoValue, Module, Str, Type};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
adt::snapshot_map::SnapshotMap,
|
adt::snapshot_map::SnapshotMap,
|
||||||
docs::{convert_docs, identify_func_docs, identify_var_docs, DocStringKind},
|
docs::{
|
||||||
|
convert_docs, identify_func_docs, identify_var_docs, DocStringKind, UntypedSymbolDocs,
|
||||||
|
VarDocsT,
|
||||||
|
},
|
||||||
syntax::{find_docs_of, get_non_strict_def_target},
|
syntax::{find_docs_of, get_non_strict_def_target},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,11 +105,22 @@ impl DocString {
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct VarDoc {
|
pub struct VarDoc {
|
||||||
/// The documentation of the variable
|
/// The documentation of the variable
|
||||||
pub docs: Option<EcoString>,
|
pub docs: EcoString,
|
||||||
/// The type of the variable
|
/// The type of the variable
|
||||||
pub ty: Option<Ty>,
|
pub ty: Option<Ty>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VarDoc {
|
||||||
|
/// Convert the variable doc to an untyped version
|
||||||
|
pub fn to_untyped(&self) -> Arc<UntypedSymbolDocs> {
|
||||||
|
Arc::new(UntypedSymbolDocs::Variable(VarDocsT {
|
||||||
|
docs: self.docs.clone(),
|
||||||
|
return_ty: (),
|
||||||
|
def_docs: OnceLock::new(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn compute_docstring(
|
pub(crate) fn compute_docstring(
|
||||||
ctx: &AnalysisContext,
|
ctx: &AnalysisContext,
|
||||||
fid: TypstFileId,
|
fid: TypstFileId,
|
||||||
|
@ -150,7 +166,7 @@ impl<'a, 'w> DocsChecker<'a, 'w> {
|
||||||
params.insert(
|
params.insert(
|
||||||
param.name.into(),
|
param.name.into(),
|
||||||
VarDoc {
|
VarDoc {
|
||||||
docs: Some(param.docs),
|
docs: param.docs,
|
||||||
ty: self.check_type_strings(&module, ¶m.types),
|
ty: self.check_type_strings(&module, ¶m.types),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::analysis::ParamAttrs;
|
use crate::analysis::ParamAttrs;
|
||||||
use crate::docs::{
|
use crate::docs::{DocStringKind, SignatureDocsT, TypelessParamDocs, UntypedSymbolDocs};
|
||||||
DocStringKind, SignatureDocsT, TidyVarDocsT, TypelessParamDocs, UntypedSymbolDocs,
|
|
||||||
};
|
|
||||||
use crate::ty::*;
|
use crate::ty::*;
|
||||||
|
|
||||||
static EMPTY_DOCSTRING: LazyLock<DocString> = LazyLock::new(DocString::default);
|
static EMPTY_DOCSTRING: LazyLock<DocString> = LazyLock::new(DocString::default);
|
||||||
|
@ -177,19 +175,11 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_ident(&mut self, root: LinkedNode<'_>, mode: InterpretMode) -> Option<Ty> {
|
fn check_ident(&mut self, root: LinkedNode<'_>, mode: InterpretMode) -> Option<Ty> {
|
||||||
let ident: ast::Ident = root.cast()?;
|
self.get_var(&root, root.cast()?).map(Ty::Var).or_else(|| {
|
||||||
let ident_ref = IdentRef {
|
let s = root.span();
|
||||||
name: ident.get().clone(),
|
let v = resolve_global_value(self.ctx, root, mode == InterpretMode::Math)?;
|
||||||
range: root.range(),
|
Some(Ty::Value(InsTy::new_at(v, s)))
|
||||||
};
|
})
|
||||||
|
|
||||||
self.get_var(root.span(), ident_ref)
|
|
||||||
.map(Ty::Var)
|
|
||||||
.or_else(|| {
|
|
||||||
let s = root.span();
|
|
||||||
let v = resolve_global_value(self.ctx, root, mode == InterpretMode::Math)?;
|
|
||||||
Some(Ty::Value(InsTy::new_at(v, s)))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_array(&mut self, root: LinkedNode<'_>) -> Option<Ty> {
|
fn check_array(&mut self, root: LinkedNode<'_>) -> Option<Ty> {
|
||||||
|
@ -372,9 +362,10 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
let closure: ast::Closure = root.cast()?;
|
let closure: ast::Closure = root.cast()?;
|
||||||
let def_id = closure
|
let def_id = closure
|
||||||
.name()
|
.name()
|
||||||
.and_then(|n| self.get_def_id(n.span(), &to_ident_ref(&root, n)?))?;
|
.and_then(|n| self.get_def_id(n.span(), &to_ident_ref(&root, n)?));
|
||||||
|
|
||||||
let docstring = self.check_docstring(&root, DocStringKind::Function, def_id);
|
let docstring =
|
||||||
|
def_id.and_then(|def_id| self.check_docstring(&root, DocStringKind::Function, def_id));
|
||||||
let docstring = docstring.as_deref().unwrap_or(&EMPTY_DOCSTRING);
|
let docstring = docstring.as_deref().unwrap_or(&EMPTY_DOCSTRING);
|
||||||
|
|
||||||
log::debug!("check closure: {:?} -> {docstring:#?}", closure.name());
|
log::debug!("check closure: {:?} -> {docstring:#?}", closure.name());
|
||||||
|
@ -398,7 +389,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
let param_doc = docstring.get_var(&name).unwrap_or(&EMPTY_VAR_DOC);
|
let param_doc = docstring.get_var(&name).unwrap_or(&EMPTY_VAR_DOC);
|
||||||
pos_docs.push(TypelessParamDocs {
|
pos_docs.push(TypelessParamDocs {
|
||||||
name,
|
name,
|
||||||
docs: param_doc.docs.clone().unwrap_or_default(),
|
docs: param_doc.docs.clone(),
|
||||||
cano_type: (),
|
cano_type: (),
|
||||||
default: None,
|
default: None,
|
||||||
attrs: ParamAttrs::positional(),
|
attrs: ParamAttrs::positional(),
|
||||||
|
@ -414,9 +405,10 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast::Param::Named(e) => {
|
ast::Param::Named(e) => {
|
||||||
let name = e.name().get().into();
|
|
||||||
let exp = self.check_expr_in(e.expr().span(), root.clone());
|
let exp = self.check_expr_in(e.expr().span(), root.clone());
|
||||||
let v = Ty::Var(self.get_var(e.name().span(), to_ident_ref(&root, e.name())?)?);
|
let var = self.get_var(&root, e.name())?;
|
||||||
|
let name = var.name.clone();
|
||||||
|
let v = Ty::Var(var.clone());
|
||||||
if let Some(annotated) = docstring.var_ty(&name) {
|
if let Some(annotated) = docstring.var_ty(&name) {
|
||||||
self.constrain(&v, annotated);
|
self.constrain(&v, annotated);
|
||||||
}
|
}
|
||||||
|
@ -431,30 +423,35 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
name.clone(),
|
name.clone(),
|
||||||
TypelessParamDocs {
|
TypelessParamDocs {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
docs: param_doc.docs.clone().unwrap_or_default(),
|
docs: param_doc.docs.clone(),
|
||||||
cano_type: (),
|
cano_type: (),
|
||||||
default: Some(e.expr().to_untyped().clone().into_text()),
|
default: Some(e.expr().to_untyped().clone().into_text()),
|
||||||
attrs: ParamAttrs::named(),
|
attrs: ParamAttrs::named(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
self.info.var_docs.insert(var.def, param_doc.to_untyped());
|
||||||
}
|
}
|
||||||
// todo: spread left/right
|
// todo: spread left/right
|
||||||
ast::Param::Spread(a) => {
|
ast::Param::Spread(a) => {
|
||||||
if let Some(e) = a.sink_ident() {
|
if let Some(e) = a.sink_ident() {
|
||||||
|
let var = self.get_var(&root, e)?;
|
||||||
|
let name = var.name.clone();
|
||||||
|
let param_doc = docstring
|
||||||
|
.get_var(&var.name.clone())
|
||||||
|
.unwrap_or(&EMPTY_VAR_DOC);
|
||||||
|
self.info.var_docs.insert(var.def, param_doc.to_untyped());
|
||||||
|
|
||||||
let exp = Ty::Builtin(BuiltinTy::Args);
|
let exp = Ty::Builtin(BuiltinTy::Args);
|
||||||
let v = Ty::Var(self.get_var(e.span(), to_ident_ref(&root, e)?)?);
|
let v = Ty::Var(var);
|
||||||
if let Some(annotated) = docstring.var_ty(&e.get().as_str().into()) {
|
if let Some(annotated) = docstring.var_ty(&name) {
|
||||||
self.constrain(&v, annotated);
|
self.constrain(&v, annotated);
|
||||||
}
|
}
|
||||||
self.constrain(&exp, &v);
|
self.constrain(&exp, &v);
|
||||||
rest = Some(v);
|
rest = Some(v);
|
||||||
|
|
||||||
let param_doc = docstring
|
|
||||||
.get_var(&e.get().as_str().into())
|
|
||||||
.unwrap_or(&EMPTY_VAR_DOC);
|
|
||||||
rest_docs = Some(TypelessParamDocs {
|
rest_docs = Some(TypelessParamDocs {
|
||||||
name: e.get().as_str().into(),
|
name,
|
||||||
docs: param_doc.docs.clone().unwrap_or_default(),
|
docs: param_doc.docs.clone(),
|
||||||
cano_type: (),
|
cano_type: (),
|
||||||
default: None,
|
default: None,
|
||||||
attrs: ParamAttrs::variadic(),
|
attrs: ParamAttrs::variadic(),
|
||||||
|
@ -474,17 +471,20 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.info.var_docs.insert(
|
// todo: arrow function docs
|
||||||
def_id,
|
if let Some(def_id) = def_id {
|
||||||
Arc::new(UntypedSymbolDocs::Function(Box::new(SignatureDocsT {
|
self.info.var_docs.insert(
|
||||||
docs: docstring.docs.clone().unwrap_or_default(),
|
def_id,
|
||||||
pos: pos_docs,
|
Arc::new(UntypedSymbolDocs::Function(Box::new(SignatureDocsT {
|
||||||
named: named_docs,
|
docs: docstring.docs.clone().unwrap_or_default(),
|
||||||
rest: rest_docs,
|
pos: pos_docs,
|
||||||
ret_ty: (),
|
named: named_docs,
|
||||||
def_docs: Default::default(),
|
rest: rest_docs,
|
||||||
}))),
|
ret_ty: (),
|
||||||
);
|
def_docs: Default::default(),
|
||||||
|
}))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let body = self.check_expr_in(closure.body().span(), root);
|
let body = self.check_expr_in(closure.body().span(), root);
|
||||||
let res_ty = if let Some(annotated) = &docstring.res_ty {
|
let res_ty = if let Some(annotated) = &docstring.res_ty {
|
||||||
|
@ -535,7 +535,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
.map(|init| self.check_expr_in(init.span(), root.clone()))
|
.map(|init| self.check_expr_in(init.span(), root.clone()))
|
||||||
.unwrap_or_else(|| Ty::Builtin(BuiltinTy::Infer));
|
.unwrap_or_else(|| Ty::Builtin(BuiltinTy::Infer));
|
||||||
|
|
||||||
let v = Ty::Var(self.get_var(c.span(), to_ident_ref(&root, c)?)?);
|
let v = Ty::Var(self.get_var(&root, c)?);
|
||||||
self.constrain(&value, &v);
|
self.constrain(&value, &v);
|
||||||
// todo lbs is the lexical signature.
|
// todo lbs is the lexical signature.
|
||||||
}
|
}
|
||||||
|
@ -553,21 +553,6 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
let value = docstring.res_ty.clone().unwrap_or(value);
|
let value = docstring.res_ty.clone().unwrap_or(value);
|
||||||
|
|
||||||
self.check_pattern(pattern, value, docstring, root.clone());
|
self.check_pattern(pattern, value, docstring, root.clone());
|
||||||
|
|
||||||
if let ast::Pattern::Normal(ast::Expr::Ident(ident)) = pattern {
|
|
||||||
let def_id = Some(ident)
|
|
||||||
.and_then(|n| self.get_def_id(n.span(), &to_ident_ref(&root, n)?));
|
|
||||||
|
|
||||||
if let Some(def_id) = def_id {
|
|
||||||
self.info.var_docs.insert(
|
|
||||||
def_id,
|
|
||||||
Arc::new(UntypedSymbolDocs::Variable(TidyVarDocsT {
|
|
||||||
docs: docstring.docs.clone().unwrap_or_default(),
|
|
||||||
return_ty: (),
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -701,14 +686,17 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||||
) -> Option<Ty> {
|
) -> Option<Ty> {
|
||||||
Some(match pattern {
|
Some(match pattern {
|
||||||
ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
|
ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
|
||||||
let var = self.get_var(ident.span(), to_ident_ref(&root, ident)?)?;
|
let var = self.get_var(&root, ident)?;
|
||||||
let annotated = docs.var_ty(&var.name);
|
let def_id = var.def;
|
||||||
|
let docstring = docs.get_var(&var.name).unwrap_or(&EMPTY_VAR_DOC);
|
||||||
let var = Ty::Var(var);
|
let var = Ty::Var(var);
|
||||||
log::debug!("check pattern: {ident:?} with {value:?} and annotation {annotated:?}");
|
log::debug!("check pattern: {ident:?} with {value:?} and docs {docstring:?}");
|
||||||
if let Some(annotated) = annotated {
|
if let Some(annotated) = docstring.ty.as_ref() {
|
||||||
self.constrain(&var, annotated);
|
self.constrain(&var, annotated);
|
||||||
}
|
}
|
||||||
self.constrain(&value, &var);
|
self.constrain(&value, &var);
|
||||||
|
|
||||||
|
self.info.var_docs.insert(def_id, docstring.to_untyped());
|
||||||
var
|
var
|
||||||
}
|
}
|
||||||
ast::Pattern::Normal(_) => Ty::Any,
|
ast::Pattern::Normal(_) => Ty::Any,
|
||||||
|
|
|
@ -8,6 +8,7 @@ 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, Func, Value};
|
use typst::foundations::{Bytes, Func, Value};
|
||||||
|
use typst::syntax::LinkedNode;
|
||||||
use typst::{
|
use typst::{
|
||||||
diag::StrResult,
|
diag::StrResult,
|
||||||
syntax::{FileId, VirtualPath},
|
syntax::{FileId, VirtualPath},
|
||||||
|
@ -16,7 +17,7 @@ use typst::{
|
||||||
use super::tidy::*;
|
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::{DocSource, Interned};
|
||||||
use crate::upstream::plain_docs_sentence;
|
use crate::upstream::plain_docs_sentence;
|
||||||
use crate::{ty::Ty, AnalysisContext};
|
use crate::{ty::Ty, AnalysisContext};
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ pub enum SymbolDocsT<T> {
|
||||||
Function(Box<SignatureDocsT<T>>),
|
Function(Box<SignatureDocsT<T>>),
|
||||||
/// Documentation about a variable.
|
/// Documentation about a variable.
|
||||||
#[serde(rename = "var")]
|
#[serde(rename = "var")]
|
||||||
Variable(TidyVarDocsT<T>),
|
Variable(VarDocsT<T>),
|
||||||
/// Documentation about a module.
|
/// Documentation about a module.
|
||||||
#[serde(rename = "module")]
|
#[serde(rename = "module")]
|
||||||
Module(TidyModuleDocs),
|
Module(TidyModuleDocs),
|
||||||
|
@ -306,6 +307,42 @@ fn format_ty(ty: Option<&Ty>, doc_ty: Option<&mut ShowTypeRepr>) -> TypeRepr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn variable_docs(ctx: &mut AnalysisContext, pos: &LinkedNode) -> Option<VarDocs> {
|
||||||
|
let source = ctx.source_by_id(pos.span().id()?).ok()?;
|
||||||
|
let type_info = ctx.type_check(&source)?;
|
||||||
|
let ty = type_info.type_of_span(pos.span())?;
|
||||||
|
|
||||||
|
// todo multiple sources
|
||||||
|
let mut srcs = ty.sources();
|
||||||
|
srcs.sort();
|
||||||
|
log::info!("check variable docs of ty: {ty:?} => {srcs:?}");
|
||||||
|
let doc_source = srcs.into_iter().next()?;
|
||||||
|
|
||||||
|
let return_ty = ty.describe().map(|short| (short, format!("{ty:?}")));
|
||||||
|
match doc_source {
|
||||||
|
DocSource::Var(var) => {
|
||||||
|
let docs = type_info
|
||||||
|
.var_docs
|
||||||
|
.get(&var.def)
|
||||||
|
.map(|docs| docs.docs().clone());
|
||||||
|
Some(VarDocs {
|
||||||
|
docs: docs.unwrap_or_default(),
|
||||||
|
return_ty,
|
||||||
|
def_docs: OnceLock::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
DocSource::Ins(ins) => ins.syntax.as_ref().map(|src| {
|
||||||
|
let docs = src.doc.as_ref().into();
|
||||||
|
VarDocs {
|
||||||
|
docs,
|
||||||
|
return_ty,
|
||||||
|
def_docs: OnceLock::new(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn signature_docs(
|
pub(crate) fn signature_docs(
|
||||||
ctx: &mut AnalysisContext,
|
ctx: &mut AnalysisContext,
|
||||||
runtime_fn: &Value,
|
runtime_fn: &Value,
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use typst::diag::StrResult;
|
use typst::diag::StrResult;
|
||||||
|
|
||||||
|
use crate::upstream::plain_docs_sentence;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct TidyParamDocs {
|
pub struct TidyParamDocs {
|
||||||
pub name: EcoString,
|
pub name: EcoString,
|
||||||
|
@ -19,14 +23,23 @@ pub struct TidyFuncDocs {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Documentation about a variable (without type information).
|
/// Documentation about a variable (without type information).
|
||||||
pub type UntypedVarDocs = TidyVarDocsT<()>;
|
pub type UntypedVarDocs = VarDocsT<()>;
|
||||||
/// Documentation about a variable.
|
/// Documentation about a variable.
|
||||||
pub type TidyVarDocs = TidyVarDocsT<Option<(String, String)>>;
|
pub type VarDocs = VarDocsT<Option<(String, String)>>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct TidyVarDocsT<T> {
|
pub struct VarDocsT<T> {
|
||||||
pub docs: EcoString,
|
pub docs: EcoString,
|
||||||
pub return_ty: T,
|
pub return_ty: T,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub def_docs: OnceLock<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VarDocs {
|
||||||
|
pub fn def_docs(&self) -> &String {
|
||||||
|
self.def_docs
|
||||||
|
.get_or_init(|| plain_docs_sentence(&self.docs).into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -146,7 +159,7 @@ pub fn identify_func_docs(converted: &str) -> StrResult<TidyFuncDocs> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn identify_var_docs(converted: EcoString) -> StrResult<TidyVarDocs> {
|
pub fn identify_var_docs(converted: EcoString) -> StrResult<VarDocs> {
|
||||||
let lines = converted.lines().collect::<Vec<_>>();
|
let lines = converted.lines().collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut return_ty = None;
|
let mut return_ty = None;
|
||||||
|
@ -180,7 +193,11 @@ pub fn identify_var_docs(converted: EcoString) -> StrResult<TidyVarDocs> {
|
||||||
None => converted,
|
None => converted,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(TidyVarDocs { docs, return_ty })
|
Ok(VarDocs {
|
||||||
|
docs,
|
||||||
|
return_ty,
|
||||||
|
def_docs: OnceLock::new(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn identify_tidy_module_docs(docs: EcoString) -> StrResult<TidyModuleDocs> {
|
pub fn identify_tidy_module_docs(docs: EcoString) -> StrResult<TidyModuleDocs> {
|
||||||
|
|
5
crates/tinymist-query/src/fixtures/hover/param.typ
Normal file
5
crates/tinymist-query/src/fixtures/hover/param.typ
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
/// Test
|
||||||
|
///
|
||||||
|
/// - param (int): The `parameter`.
|
||||||
|
#let f(/* ident after */ param: 1) = 1;
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
source: crates/tinymist-query/src/hover.rs
|
||||||
|
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||||
|
input_file: crates/tinymist-query/src/fixtures/hover/param.typ
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"contents": "```typc\nlet param;\n```\n\n---\nThe `parameter`.",
|
||||||
|
"range": "3:25:3:30"
|
||||||
|
}
|
|
@ -259,7 +259,8 @@ fn def_tooltip(
|
||||||
"let {name}({params}){result};",
|
"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())
|
result =
|
||||||
|
ResultTooltip(&lnk.name, sig.as_ref().and_then(|sig| sig.ret_ty.as_ref()))
|
||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -276,6 +277,8 @@ fn def_tooltip(
|
||||||
}
|
}
|
||||||
LexicalKind::Var(LexicalVarKind::Variable) => {
|
LexicalKind::Var(LexicalVarKind::Variable) => {
|
||||||
let deref_node = deref_target.node();
|
let deref_node = deref_target.node();
|
||||||
|
let sig = ctx.variable_docs(deref_target.node());
|
||||||
|
|
||||||
// todo: check sensible length, value highlighting
|
// todo: check sensible length, value highlighting
|
||||||
if let Some(values) = expr_tooltip(ctx.world(), deref_node) {
|
if let Some(values) = expr_tooltip(ctx.world(), deref_node) {
|
||||||
match values {
|
match values {
|
||||||
|
@ -293,10 +296,19 @@ fn def_tooltip(
|
||||||
|
|
||||||
results.push(MarkedString::LanguageString(LanguageString {
|
results.push(MarkedString::LanguageString(LanguageString {
|
||||||
language: "typc".to_owned(),
|
language: "typc".to_owned(),
|
||||||
value: format!("let {name};", name = lnk.name),
|
value: format!(
|
||||||
|
"let {name}{result};",
|
||||||
|
name = lnk.name,
|
||||||
|
result = ResultTooltip(
|
||||||
|
&lnk.name,
|
||||||
|
sig.as_ref().and_then(|sig| sig.return_ty.as_ref())
|
||||||
|
)
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if let Some(doc) = DocTooltip::get(ctx, &lnk) {
|
if let Some(doc) = sig {
|
||||||
|
results.push(MarkedString::String(doc.def_docs().clone()));
|
||||||
|
} else if let Some(doc) = DocTooltip::get(ctx, &lnk) {
|
||||||
results.push(MarkedString::String(doc));
|
results.push(MarkedString::String(doc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,22 +373,18 @@ impl fmt::Display for ParamTooltip<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResultTooltip<'a>(&'a str, Option<&'a SignatureDocs>);
|
struct ResultTooltip<'a>(&'a str, Option<&'a (String, String)>);
|
||||||
|
|
||||||
impl fmt::Display for ResultTooltip<'_> {
|
impl fmt::Display for ResultTooltip<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let Some(sig) = self.1 else {
|
let Some((short, _)) = self.1 else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
if let Some((short, _)) = &sig.ret_ty {
|
if short == self.0 {
|
||||||
if short == self.0 {
|
return Ok(());
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, " = {short}")
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write!(f, " = {short}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -273,7 +273,14 @@ pub fn find_test_position_(s: &Source, offset: usize) -> LspPosition {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if matches!(n.kind(), SyntaxKind::Named) {
|
if matches!(n.kind(), SyntaxKind::Named) {
|
||||||
n = n.children().last().unwrap();
|
if match_ident {
|
||||||
|
n = n
|
||||||
|
.children()
|
||||||
|
.find(|n| matches!(n.kind(), SyntaxKind::Ident))
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
n = n.children().last().unwrap();
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if match_ident {
|
if match_ident {
|
||||||
|
|
|
@ -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:31b50bcd716fccacfd71057fd764a7bc");
|
insta::assert_snapshot!(hash, @"siphash128_13:7ae5ec9c50b480649a00306d1359d9f0");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -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:801a7b1171fad3200bbe8082f50a055f");
|
insta::assert_snapshot!(hash, @"siphash128_13:4880731c9a64e74b858da04feede2cb3");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue