1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use crate::common::DocumentCache;
5use i_slint_compiler::expression_tree::Callable;
6use i_slint_compiler::langtype::{Function, Type};
7use i_slint_compiler::lookup::{LookupObject as _, LookupResult, LookupResultCallable};
8use i_slint_compiler::namedreference::NamedReference;
9use i_slint_compiler::parser::{syntax_nodes, SyntaxKind, SyntaxNode, SyntaxToken};
10use lsp_types::{ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation};
11
12pub(crate) fn get_signature_help(
13 document_cache: &mut DocumentCache,
14 token: SyntaxToken,
15) -> Option<SignatureHelp> {
16 let pos = token.text_range().start();
17 let mut node = token.parent();
18 let mut result = vec![];
19
20 loop {
21 if let Some(node) = syntax_nodes::FunctionCallExpression::new(node.clone()) {
22 if let Some(f) = node.Expression().next() {
23 let mut active_parameter = None;
24 for t in node.children_with_tokens() {
25 if t.text_range().start() > pos {
26 break;
27 }
28 match t.kind() {
29 SyntaxKind::LParent => {
30 active_parameter = Some(0);
31 }
32 SyntaxKind::Comma => {
33 if let Some(active_parameter) = active_parameter.as_mut() {
34 *active_parameter += 1;
35 }
36 }
37 SyntaxKind::RParent => {
38 active_parameter = None;
39 }
40 _ => (),
41 };
42 }
43 if let Some(si) = signature_info(document_cache, f.into(), active_parameter) {
44 result.push(si);
45 }
46 }
47 }
48
49 match node.parent() {
50 Some(n) => node = n,
51 None => return make_result(result),
52 }
53 }
54}
55
56fn make_result(result: Vec<SignatureInformation>) -> Option<SignatureHelp> {
57 if result.is_empty() {
58 return None;
59 }
60 Some(SignatureHelp { signatures: result, active_signature: None, active_parameter: None })
61}
62
63fn signature_info(
64 document_cache: &mut DocumentCache,
65 func_expr: SyntaxNode,
66 active_parameter: Option<u32>,
67) -> Option<SignatureInformation> {
68 if let Some(sub_expr) = func_expr.child_node(SyntaxKind::Expression) {
69 return signature_info(document_cache, sub_expr, active_parameter);
70 }
71 let qn = func_expr.child_node(SyntaxKind::QualifiedName)?;
72 let lr = crate::util::with_lookup_ctx(document_cache, func_expr, |ctx| {
73 let mut it = qn
74 .children_with_tokens()
75 .filter_map(|t| t.into_token())
76 .filter(|t| t.kind() == SyntaxKind::Identifier);
77 let first_tok = it.next()?;
78 let mut expr_it = i_slint_compiler::lookup::global_lookup()
79 .lookup(ctx, &i_slint_compiler::parser::normalize_identifier(first_tok.text()))?;
80 for cur_tok in it {
81 expr_it = expr_it
82 .lookup(ctx, &i_slint_compiler::parser::normalize_identifier(cur_tok.text()))?;
83 }
84 Some(expr_it)
85 })?;
86 let LookupResult::Callable(callable) = lr? else { return None };
87
88 match callable {
89 LookupResultCallable::Callable(Callable::Callback(nr))
90 | LookupResultCallable::Callable(Callable::Function(nr)) => {
91 signature_from_nr(nr, active_parameter)
92 }
93 LookupResultCallable::Callable(Callable::Builtin(b)) => {
94 Some(signature_from_function_ty(&format!("{b:?}"), &b.ty(), 0, active_parameter))
95 }
96 LookupResultCallable::Macro(b) => {
97 Some(make_signature_info(&format!("{b:?}"), vec!["...".into()], active_parameter))
98 }
99 LookupResultCallable::MemberFunction { member, .. } => match *member {
100 LookupResultCallable::Callable(Callable::Builtin(b)) => {
101 Some(signature_from_function_ty(&format!("{b:?}"), &b.ty(), 1, active_parameter))
102 }
103 LookupResultCallable::Macro(b) => {
104 Some(make_signature_info(&format!("{b:?}"), vec!["...".into()], active_parameter))
105 }
106 _ => None,
107 },
108 }
109}
110
111fn signature_from_nr(
112 nr: NamedReference,
113 active_parameter: Option<u32>,
114) -> Option<SignatureInformation> {
115 match nr.ty() {
116 Type::Function(f: Rc) | Type::Callback(f: Rc) => {
117 Some(signature_from_function_ty(nr.name(), &f, skip:0, active_parameter))
118 }
119 _ => None,
120 }
121}
122
123fn signature_from_function_ty(
124 name: &str,
125 f: &Function,
126 skip: usize,
127 active_parameter: Option<u32>,
128) -> SignatureInformation {
129 make_signature_info(
130 name,
131 args:f.args
132 .iter()
133 .zip(f.arg_names.iter().chain(std::iter::repeat(&Default::default())))
134 .skip(skip)
135 .filter(|(x: &&Type, _)| *x != &Type::ElementReference)
136 .map(
137 |(ty: &Type, name: &SmolStr)| {
138 if !name.is_empty() {
139 format!("{name}: {ty}")
140 } else {
141 ty.to_string()
142 }
143 },
144 )
145 .collect(),
146 active_parameter,
147 )
148}
149
150fn make_signature_info(
151 name: &str,
152 args: Vec<String>,
153 active_parameter: Option<u32>,
154) -> SignatureInformation {
155 SignatureInformation {
156 label: format!("{}({})", name, args.join(", ")),
157 documentation: None,
158 parameters: Some(
159 argsimpl Iterator.into_iter()
160 .map(|x: String| ParameterInformation {
161 label: ParameterLabel::Simple(x),
162 documentation: None,
163 })
164 .collect(),
165 ),
166 active_parameter,
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 /// Given a source text containing the unicode emoji `🔺`, the emoji will be removed and then an signature help request will be done as if the cursor was there
175 fn query_signature_help(file: &str) -> Option<SignatureHelp> {
176 const CURSOR_EMOJI: char = '🔺';
177 let offset = (file.find(CURSOR_EMOJI).unwrap() as u32).into();
178 let source = file.replace(CURSOR_EMOJI, "");
179 let (mut dc, uri, _) = crate::language::test::loaded_document_cache(source);
180
181 let doc = dc.get_document(&uri).unwrap();
182 let token = crate::language::token_at_offset(doc.node.as_ref().unwrap(), offset)?;
183 get_signature_help(&mut dc, token)
184 }
185
186 #[test]
187 fn builtin_function_and_member_fn() {
188 let source = r#"
189import { StandardTableView } from "std-widgets.slint";
190export component Abc {
191 table := StandardTableView {}
192 function do_something(a: int, b: int) -> int {
193 table.accessible-action-set-value(a.sqrt(🔺
194 45
195 }
196}
197 "#;
198 let sh = query_signature_help(source).unwrap();
199 assert_eq!(
200 sh,
201 SignatureHelp {
202 signatures: vec![
203 SignatureInformation {
204 label: "Sqrt()".into(),
205 documentation: None,
206 parameters: Some(vec![]),
207 active_parameter: Some(0),
208 },
209 make_signature_info(
210 "accessible-action-set-value",
211 vec!["string".into()],
212 Some(0)
213 ),
214 ],
215 active_signature: None,
216 active_parameter: None
217 }
218 );
219 }
220
221 #[test]
222 fn arg_pos() {
223 let source = r#"
224export component Abc {
225 function abc(a: {hi: string, ho: int}, b: int, c : string) -> int { b }
226 function do_something(a: int, b: int) -> int {
227 pow(42, abc({hi: "hello", ho: int}, a.pow(🔺
228 }
229}
230 "#;
231 let sh = query_signature_help(source).unwrap();
232 assert_eq!(
233 sh,
234 SignatureHelp {
235 signatures: vec![
236 make_signature_info("Pow", vec!["float".into()], Some(0),),
237 make_signature_info(
238 "abc",
239 vec![
240 "a: { hi: string,ho: int,}".into(),
241 "b: int".into(),
242 "c: string".into()
243 ],
244 Some(1)
245 ),
246 make_signature_info("Pow", vec!["float".into(), "float".into()], Some(1)),
247 ],
248 active_signature: None,
249 active_parameter: None
250 }
251 );
252 }
253
254 #[test]
255 fn callback_with_names() {
256 let source = r#"
257 import { StandardTableView } from "std-widgets.slint";
258 export component Abc {
259 table := StandardTableView {}
260 function do_something(a: int, b: int) -> int {
261 table.current_row_changed(🔺)
262 }
263 }
264
265 "#;
266 let sh = query_signature_help(source).unwrap();
267 assert_eq!(
268 sh,
269 SignatureHelp {
270 signatures: vec![make_signature_info(
271 "current-row-changed",
272 vec!["current-row: int".into()],
273 Some(0),
274 )],
275 active_signature: None,
276 active_parameter: None
277 }
278 );
279 }
280
281 #[test]
282 fn macros() {
283 let source = r#"
284 export component Abc {
285 function do_something(a: int, b: int) -> int {
286 debug(0, 1, a.mod(b.clamp(0,🔺 )), 3, 4)
287 }
288 }
289
290 "#;
291 let sh = query_signature_help(source).unwrap();
292 assert_eq!(
293 sh,
294 SignatureHelp {
295 signatures: vec![
296 make_signature_info("Clamp", vec!["...".into()], Some(1)),
297 make_signature_info("Mod", vec!["...".into()], Some(0)),
298 make_signature_info("Debug", vec!["...".into()], Some(2)),
299 ],
300 active_signature: None,
301 active_parameter: None
302 }
303 );
304 }
305}
306