From 3490a3244c537cd684a737056c74f5b808c263f2 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Sun, 5 May 2024 23:30:19 +0800 Subject: [PATCH] feat: reimplement signature help (#241) * feat: reimplement signature help * dev: export doc tooltip * dev: update snapshot --- crates/tinymist-query/src/hover.rs | 4 +- crates/tinymist-query/src/signature_help.rs | 138 ++++++++++++-------- crates/tinymist-query/src/syntax/matcher.rs | 5 +- tests/e2e/main.rs | 2 +- 4 files changed, 89 insertions(+), 60 deletions(-) diff --git a/crates/tinymist-query/src/hover.rs b/crates/tinymist-query/src/hover.rs index a159f001..39584821 100644 --- a/crates/tinymist-query/src/hover.rs +++ b/crates/tinymist-query/src/hover.rs @@ -353,10 +353,10 @@ impl ExternalDocLink { } } -struct DocTooltip; +pub(crate) struct DocTooltip; impl DocTooltip { - fn get(ctx: &mut AnalysisContext, lnk: &DefinitionLink) -> Option { + pub fn get(ctx: &mut AnalysisContext, lnk: &DefinitionLink) -> Option { self::DocTooltip::get_inner(ctx, lnk).map(|s| "\n\n".to_owned() + &s) } diff --git a/crates/tinymist-query/src/signature_help.rs b/crates/tinymist-query/src/signature_help.rs index b36062f5..1b2cafd6 100644 --- a/crates/tinymist-query/src/signature_help.rs +++ b/crates/tinymist-query/src/signature_help.rs @@ -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 { 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(""), - 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::>(); + 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::()?; - let grand = parent.parent()?; - let expr = grand.cast::()?; - 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, diff --git a/crates/tinymist-query/src/syntax/matcher.rs b/crates/tinymist-query/src/syntax/matcher.rs index f28d6d7a..8190e4be 100644 --- a/crates/tinymist-query/src/syntax/matcher.rs +++ b/crates/tinymist-query/src/syntax/matcher.rs @@ -354,9 +354,12 @@ pub fn get_check_target(node: LinkedNode) -> Option> { fn get_param_target<'a>( args_node: LinkedNode<'a>, - node: LinkedNode<'a>, + mut node: LinkedNode<'a>, param_kind: ParamKind, ) -> Option> { + if node.kind() == SyntaxKind::RightParen { + node = node.prev_sibling()?; + } match node.kind() { SyntaxKind::Named => { let param_ident = node.cast::()?.name(); diff --git a/tests/e2e/main.rs b/tests/e2e/main.rs index 550807d9..22ae953b 100644 --- a/tests/e2e/main.rs +++ b/tests/e2e/main.rs @@ -385,7 +385,7 @@ fn e2e() { }); let hash = replay_log(&tinymist_binary, &root.join("vscode")); - insta::assert_snapshot!(hash, @"siphash128_13:5f118cda0949759f157a2c22b9433775"); + insta::assert_snapshot!(hash, @"siphash128_13:ab459e29e562f52a4594eb784b592341"); } }