mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 05:05:00 +00:00
feat: reimplement signature help (#241)
* feat: reimplement signature help * dev: export doc tooltip * dev: update snapshot
This commit is contained in:
parent
b8143e7090
commit
3490a3244c
4 changed files with 89 additions and 60 deletions
|
@ -1,4 +1,9 @@
|
|||
use crate::{prelude::*, syntax::param_index_at_leaf, SemanticRequest};
|
||||
use crate::{
|
||||
analysis::{analyze_dyn_signature, find_definition, FlowType},
|
||||
prelude::*,
|
||||
syntax::{get_check_target, get_deref_target, CheckTarget, ParamTarget},
|
||||
DocTooltip, LspParamInfo, SemanticRequest,
|
||||
};
|
||||
|
||||
/// The [`textDocument/signatureHelp`] request is sent from the client to the
|
||||
/// server to request signature information at a given cursor position.
|
||||
|
@ -17,58 +22,98 @@ impl SemanticRequest for SignatureHelpRequest {
|
|||
|
||||
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
||||
let source = ctx.source_by_path(&self.path).ok()?;
|
||||
let typst_offset = ctx.to_typst_pos(self.position, &source)?;
|
||||
let cursor = ctx.to_typst_pos(self.position, &source)? + 1;
|
||||
|
||||
let ast_node = LinkedNode::new(source.root()).leaf_at(typst_offset + 1)?;
|
||||
let (callee, callee_node, args) = surrounding_function_syntax(&ast_node)?;
|
||||
|
||||
if !callee.hash() && !matches!(callee, ast::Expr::MathIdent(_)) {
|
||||
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
||||
let CheckTarget::Param { callee, target, .. } = get_check_target(ast_node)? else {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let values = analyze_expr(ctx.world(), &callee_node);
|
||||
let deref_target = get_deref_target(callee, cursor)?;
|
||||
|
||||
let function = values.into_iter().find_map(|v| match v.0 {
|
||||
Value::Func(f) => Some(f),
|
||||
_ => None,
|
||||
})?;
|
||||
let def_link = find_definition(ctx, source.clone(), None, deref_target)?;
|
||||
|
||||
let documentation = DocTooltip::get(ctx, &def_link)
|
||||
.as_deref()
|
||||
.map(markdown_docs);
|
||||
|
||||
let Some(Value::Func(function)) = def_link.value else {
|
||||
return None;
|
||||
};
|
||||
trace!("got function {function:?}");
|
||||
|
||||
let param_index = param_index_at_leaf(&ast_node, &function, args);
|
||||
let mut function = &function;
|
||||
use typst::foundations::func::Repr;
|
||||
let mut param_shift = 0;
|
||||
while let Repr::With(inner) = function.inner() {
|
||||
param_shift += inner.1.items.iter().filter(|x| x.name.is_none()).count();
|
||||
function = &inner.0;
|
||||
}
|
||||
|
||||
let label = format!(
|
||||
"{}({}){}",
|
||||
function.name().unwrap_or("<anonymous closure>"),
|
||||
match function.params() {
|
||||
Some(params) => params
|
||||
let sig = analyze_dyn_signature(ctx, function.clone());
|
||||
let pos = &sig.primary().pos;
|
||||
let mut named = sig.primary().named.values().collect::<Vec<_>>();
|
||||
let rest = &sig.primary().rest;
|
||||
|
||||
named.sort_by_key(|x| &x.name);
|
||||
|
||||
let active_parameter = match &target {
|
||||
ParamTarget::Positional { positional, .. } => Some((*positional) + param_shift),
|
||||
ParamTarget::Named(name) => {
|
||||
let name = name.get().clone().into_text();
|
||||
named
|
||||
.iter()
|
||||
.map(typst_to_lsp::param_info_to_label)
|
||||
.join(", "),
|
||||
None => "".to_owned(),
|
||||
},
|
||||
match function.returns() {
|
||||
Some(returns) => format!("-> {}", typst_to_lsp::cast_info_to_label(returns)),
|
||||
None => "".to_owned(),
|
||||
.position(|x| x.name.as_ref() == name.as_ref())
|
||||
.map(|i| pos.len() + i)
|
||||
}
|
||||
);
|
||||
let params = function
|
||||
.params()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(typst_to_lsp::param_info)
|
||||
.collect();
|
||||
};
|
||||
|
||||
let mut label = def_link.name.clone();
|
||||
let mut params = Vec::new();
|
||||
|
||||
label.push('(');
|
||||
for ty in pos.iter().chain(named.into_iter()).chain(rest.iter()) {
|
||||
if !params.is_empty() {
|
||||
label.push_str(", ");
|
||||
}
|
||||
|
||||
label.push_str(&format!(
|
||||
"{}: {}",
|
||||
ty.name,
|
||||
ty.infer_type
|
||||
.as_ref()
|
||||
.unwrap_or(&FlowType::Any)
|
||||
.describe()
|
||||
.as_deref()
|
||||
.unwrap_or("any")
|
||||
));
|
||||
|
||||
params.push(LspParamInfo {
|
||||
label: lsp_types::ParameterLabel::Simple(ty.name.clone().into()),
|
||||
documentation: if !ty.docs.is_empty() {
|
||||
Some(Documentation::MarkupContent(MarkupContent {
|
||||
value: ty.docs.clone().into(),
|
||||
kind: MarkupKind::Markdown,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
});
|
||||
}
|
||||
label.push(')');
|
||||
if let Some(ret_ty) = sig.primary().ret_ty.as_ref() {
|
||||
label.push_str(" -> ");
|
||||
label.push_str(ret_ty.describe().as_deref().unwrap_or("any"));
|
||||
}
|
||||
|
||||
trace!("got signature info {label} {params:?}");
|
||||
|
||||
let documentation = function.docs().map(markdown_docs);
|
||||
|
||||
let active_parameter = param_index.map(|i| i as u32);
|
||||
|
||||
Some(SignatureHelp {
|
||||
signatures: vec![SignatureInformation {
|
||||
label,
|
||||
documentation,
|
||||
parameters: Some(params),
|
||||
active_parameter,
|
||||
active_parameter: active_parameter.map(|x| x as u32),
|
||||
}],
|
||||
active_signature: Some(0),
|
||||
active_parameter: None,
|
||||
|
@ -76,25 +121,6 @@ impl SemanticRequest for SignatureHelpRequest {
|
|||
}
|
||||
}
|
||||
|
||||
fn surrounding_function_syntax<'b>(
|
||||
leaf: &'b LinkedNode,
|
||||
) -> Option<(ast::Expr<'b>, LinkedNode<'b>, ast::Args<'b>)> {
|
||||
let parent = leaf.parent()?;
|
||||
let parent = match parent.kind() {
|
||||
SyntaxKind::Named => parent.parent()?,
|
||||
_ => parent,
|
||||
};
|
||||
let args = parent.cast::<ast::Args>()?;
|
||||
let grand = parent.parent()?;
|
||||
let expr = grand.cast::<ast::Expr>()?;
|
||||
let callee = match expr {
|
||||
ast::Expr::FuncCall(call) => call.callee(),
|
||||
ast::Expr::Set(set) => set.target(),
|
||||
_ => return None,
|
||||
};
|
||||
Some((callee, grand.find(callee.span())?, args))
|
||||
}
|
||||
|
||||
fn markdown_docs(docs: &str) -> Documentation {
|
||||
Documentation::MarkupContent(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue