feat: reimplement signature help (#241)

* feat: reimplement signature help

* dev: export doc tooltip

* dev: update snapshot
This commit is contained in:
Myriad-Dreamin 2024-05-05 23:30:19 +08:00 committed by GitHub
parent b8143e7090
commit 3490a3244c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 89 additions and 60 deletions

View file

@ -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,