mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
178 lines
5.9 KiB
Rust
178 lines
5.9 KiB
Rust
use once_cell::sync::OnceCell;
|
|
|
|
use crate::{
|
|
adt::interner::Interned,
|
|
analysis::{analyze_dyn_signature, find_definition, Ty},
|
|
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.
|
|
///
|
|
/// [`textDocument/signatureHelp`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_signatureHelp
|
|
#[derive(Debug, Clone)]
|
|
pub struct SignatureHelpRequest {
|
|
/// The path of the document to get signature help for.
|
|
pub path: PathBuf,
|
|
/// The position of the cursor to get signature help for.
|
|
pub position: LspPosition,
|
|
}
|
|
|
|
impl SemanticRequest for SignatureHelpRequest {
|
|
type Response = SignatureHelp;
|
|
|
|
fn request(self, ctx: &mut AnalysisContext) -> Option<Self::Response> {
|
|
let source = ctx.source_by_path(&self.path).ok()?;
|
|
let cursor = ctx.to_typst_pos(self.position, &source)? + 1;
|
|
|
|
let ast_node = LinkedNode::new(source.root()).leaf_at(cursor)?;
|
|
let CheckTarget::Param {
|
|
callee,
|
|
target,
|
|
is_set,
|
|
..
|
|
} = get_check_target(ast_node)?
|
|
else {
|
|
return None;
|
|
};
|
|
|
|
let deref_target = get_deref_target(callee, cursor)?;
|
|
|
|
let def_link = find_definition(ctx, source.clone(), None, deref_target)?;
|
|
|
|
let type_sig = ctx.user_type_of_def(&source, &def_link);
|
|
|
|
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 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 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;
|
|
|
|
let type_sig = type_sig.and_then(|type_sig| type_sig.sig_repr(true));
|
|
|
|
log::info!("got type signature {type_sig:?}");
|
|
|
|
named.sort_by_key(|x| &x.name);
|
|
|
|
let mut active_parameter = None;
|
|
|
|
let mut label = def_link.name.clone();
|
|
let mut params = Vec::new();
|
|
|
|
label.push('(');
|
|
let pos = pos
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, pos)| (pos, type_sig.as_ref().and_then(|sig| sig.pos(i))));
|
|
let named = named
|
|
.into_iter()
|
|
.map(|x| (x, type_sig.as_ref().and_then(|sig| sig.named(&x.name))));
|
|
let rest = rest
|
|
.iter()
|
|
.map(|x| (x, type_sig.as_ref().and_then(|sig| sig.rest_param())));
|
|
|
|
let mut real_offset = 0;
|
|
let focus_name = OnceCell::new();
|
|
for (i, (param, ty)) in pos.chain(named).chain(rest).enumerate() {
|
|
if is_set && !param.settable {
|
|
continue;
|
|
}
|
|
|
|
match &target {
|
|
ParamTarget::Positional { .. } if is_set => {}
|
|
ParamTarget::Positional { positional, .. } => {
|
|
if (*positional) + param_shift == i {
|
|
active_parameter = Some(real_offset);
|
|
}
|
|
}
|
|
ParamTarget::Named(name) => {
|
|
let focus_name = focus_name
|
|
.get_or_init(|| Interned::new_str(&name.get().clone().into_text()));
|
|
if focus_name == ¶m.name {
|
|
active_parameter = Some(real_offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
real_offset += 1;
|
|
|
|
if !params.is_empty() {
|
|
label.push_str(", ");
|
|
}
|
|
|
|
label.push_str(&format!(
|
|
"{}: {}",
|
|
param.name,
|
|
ty.or_else(|| param.base_type.as_ref())
|
|
.unwrap_or(&Ty::Any)
|
|
.describe()
|
|
.as_deref()
|
|
.unwrap_or("any")
|
|
));
|
|
|
|
params.push(LspParamInfo {
|
|
label: lsp_types::ParameterLabel::Simple(param.name.as_ref().into()),
|
|
documentation: if !param.docs.is_empty() {
|
|
Some(Documentation::MarkupContent(MarkupContent {
|
|
value: param.docs.clone().into(),
|
|
kind: MarkupKind::Markdown,
|
|
}))
|
|
} else {
|
|
None
|
|
},
|
|
});
|
|
}
|
|
label.push(')');
|
|
let ret = type_sig
|
|
.as_ref()
|
|
.and_then(|sig| sig.ret.as_ref())
|
|
.or_else(|| sig.primary().ret_ty.as_ref());
|
|
if let Some(ret_ty) = ret {
|
|
label.push_str(" -> ");
|
|
label.push_str(ret_ty.describe().as_deref().unwrap_or("any"));
|
|
}
|
|
|
|
if matches!(target, ParamTarget::Positional { .. }) {
|
|
active_parameter =
|
|
active_parameter.map(|x| x.min(sig.primary().pos.len().saturating_sub(1)));
|
|
}
|
|
|
|
trace!("got signature info {label} {params:?}");
|
|
|
|
Some(SignatureHelp {
|
|
signatures: vec![SignatureInformation {
|
|
label,
|
|
documentation,
|
|
parameters: Some(params),
|
|
active_parameter: active_parameter.map(|x| x as u32),
|
|
}],
|
|
active_signature: Some(0),
|
|
active_parameter: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn markdown_docs(docs: &str) -> Documentation {
|
|
Documentation::MarkupContent(MarkupContent {
|
|
kind: MarkupKind::Markdown,
|
|
value: docs.to_owned(),
|
|
})
|
|
}
|