1use std::{ops::Range, sync::Arc};
2
3use gpui::FontWeight;
4use language::{
5 markdown::{MarkdownHighlight, MarkdownHighlightStyle},
6 Language,
7};
8
9pub const SIGNATURE_HELP_HIGHLIGHT_CURRENT: MarkdownHighlight =
10 MarkdownHighlight::Style(MarkdownHighlightStyle {
11 italic: false,
12 underline: false,
13 strikethrough: false,
14 weight: FontWeight::EXTRA_BOLD,
15 });
16
17pub const SIGNATURE_HELP_HIGHLIGHT_OVERLOAD: MarkdownHighlight =
18 MarkdownHighlight::Style(MarkdownHighlightStyle {
19 italic: true,
20 underline: false,
21 strikethrough: false,
22 weight: FontWeight::NORMAL,
23 });
24
25#[derive(Debug)]
26pub struct SignatureHelp {
27 pub markdown: String,
28 pub highlights: Vec<(Range<usize>, MarkdownHighlight)>,
29}
30
31impl SignatureHelp {
32 pub fn new(
33 lsp::SignatureHelp {
34 signatures,
35 active_signature,
36 active_parameter,
37 ..
38 }: lsp::SignatureHelp,
39 language: Option<Arc<Language>>,
40 ) -> Option<Self> {
41 let function_options_count = signatures.len();
42
43 let signature_information = active_signature
44 .and_then(|active_signature| signatures.get(active_signature as usize))
45 .or_else(|| signatures.first())?;
46
47 let str_for_join = ", ";
48 let parameter_length = signature_information
49 .parameters
50 .as_ref()
51 .map(|parameters| parameters.len())
52 .unwrap_or(0);
53 let mut highlight_start = 0;
54 let (markdown, mut highlights): (Vec<_>, Vec<_>) = signature_information
55 .parameters
56 .as_ref()?
57 .iter()
58 .enumerate()
59 .filter_map(|(i, parameter_information)| {
60 let string = match parameter_information.label.clone() {
61 lsp::ParameterLabel::Simple(string) => string,
62 lsp::ParameterLabel::LabelOffsets(offset) => signature_information
63 .label
64 .chars()
65 .skip(offset[0] as usize)
66 .take((offset[1] - offset[0]) as usize)
67 .collect::<String>(),
68 };
69 let string_length = string.len();
70
71 let result = if let Some(active_parameter) = active_parameter {
72 if i == active_parameter as usize {
73 Some((
74 string,
75 Some((
76 highlight_start..(highlight_start + string_length),
77 SIGNATURE_HELP_HIGHLIGHT_CURRENT,
78 )),
79 ))
80 } else {
81 Some((string, None))
82 }
83 } else {
84 Some((string, None))
85 };
86
87 if i != parameter_length {
88 highlight_start += string_length + str_for_join.len();
89 }
90
91 result
92 })
93 .unzip();
94
95 let result = if markdown.is_empty() {
96 None
97 } else {
98 let markdown = markdown.join(str_for_join);
99 let language_name = language
100 .map(|n| n.name().to_lowercase())
101 .unwrap_or_default();
102
103 let markdown = if function_options_count >= 2 {
104 let suffix = format!("(+{} overload)", function_options_count - 1);
105 let highlight_start = markdown.len() + 1;
106 highlights.push(Some((
107 highlight_start..(highlight_start + suffix.len()),
108 SIGNATURE_HELP_HIGHLIGHT_OVERLOAD,
109 )));
110 format!("```{language_name}\n{markdown} {suffix}")
111 } else {
112 format!("```{language_name}\n{markdown}")
113 };
114
115 Some((markdown, highlights.into_iter().flatten().collect()))
116 };
117
118 result.map(|(markdown, highlights)| Self {
119 markdown,
120 highlights,
121 })
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use crate::lsp_command::signature_help::{
128 SignatureHelp, SIGNATURE_HELP_HIGHLIGHT_CURRENT, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD,
129 };
130
131 #[test]
132 fn test_create_signature_help_markdown_string_1() {
133 let signature_help = lsp::SignatureHelp {
134 signatures: vec![lsp::SignatureInformation {
135 label: "fn test(foo: u8, bar: &str)".to_string(),
136 documentation: None,
137 parameters: Some(vec![
138 lsp::ParameterInformation {
139 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
140 documentation: None,
141 },
142 lsp::ParameterInformation {
143 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
144 documentation: None,
145 },
146 ]),
147 active_parameter: None,
148 }],
149 active_signature: Some(0),
150 active_parameter: Some(0),
151 };
152 let maybe_markdown = SignatureHelp::new(signature_help, None);
153 assert!(maybe_markdown.is_some());
154
155 let markdown = maybe_markdown.unwrap();
156 let markdown = (markdown.markdown, markdown.highlights);
157 assert_eq!(
158 markdown,
159 (
160 "```\nfoo: u8, bar: &str".to_string(),
161 vec![(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
162 )
163 );
164 }
165
166 #[test]
167 fn test_create_signature_help_markdown_string_2() {
168 let signature_help = lsp::SignatureHelp {
169 signatures: vec![lsp::SignatureInformation {
170 label: "fn test(foo: u8, bar: &str)".to_string(),
171 documentation: None,
172 parameters: Some(vec![
173 lsp::ParameterInformation {
174 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
175 documentation: None,
176 },
177 lsp::ParameterInformation {
178 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
179 documentation: None,
180 },
181 ]),
182 active_parameter: None,
183 }],
184 active_signature: Some(0),
185 active_parameter: Some(1),
186 };
187 let maybe_markdown = SignatureHelp::new(signature_help, None);
188 assert!(maybe_markdown.is_some());
189
190 let markdown = maybe_markdown.unwrap();
191 let markdown = (markdown.markdown, markdown.highlights);
192 assert_eq!(
193 markdown,
194 (
195 "```\nfoo: u8, bar: &str".to_string(),
196 vec![(9..18, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
197 )
198 );
199 }
200
201 #[test]
202 fn test_create_signature_help_markdown_string_3() {
203 let signature_help = lsp::SignatureHelp {
204 signatures: vec![
205 lsp::SignatureInformation {
206 label: "fn test1(foo: u8, bar: &str)".to_string(),
207 documentation: None,
208 parameters: Some(vec![
209 lsp::ParameterInformation {
210 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
211 documentation: None,
212 },
213 lsp::ParameterInformation {
214 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
215 documentation: None,
216 },
217 ]),
218 active_parameter: None,
219 },
220 lsp::SignatureInformation {
221 label: "fn test2(hoge: String, fuga: bool)".to_string(),
222 documentation: None,
223 parameters: Some(vec![
224 lsp::ParameterInformation {
225 label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
226 documentation: None,
227 },
228 lsp::ParameterInformation {
229 label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
230 documentation: None,
231 },
232 ]),
233 active_parameter: None,
234 },
235 ],
236 active_signature: Some(0),
237 active_parameter: Some(0),
238 };
239 let maybe_markdown = SignatureHelp::new(signature_help, None);
240 assert!(maybe_markdown.is_some());
241
242 let markdown = maybe_markdown.unwrap();
243 let markdown = (markdown.markdown, markdown.highlights);
244 assert_eq!(
245 markdown,
246 (
247 "```\nfoo: u8, bar: &str (+1 overload)".to_string(),
248 vec![
249 (0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
250 (19..32, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
251 ]
252 )
253 );
254 }
255
256 #[test]
257 fn test_create_signature_help_markdown_string_4() {
258 let signature_help = lsp::SignatureHelp {
259 signatures: vec![
260 lsp::SignatureInformation {
261 label: "fn test1(foo: u8, bar: &str)".to_string(),
262 documentation: None,
263 parameters: Some(vec![
264 lsp::ParameterInformation {
265 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
266 documentation: None,
267 },
268 lsp::ParameterInformation {
269 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
270 documentation: None,
271 },
272 ]),
273 active_parameter: None,
274 },
275 lsp::SignatureInformation {
276 label: "fn test2(hoge: String, fuga: bool)".to_string(),
277 documentation: None,
278 parameters: Some(vec![
279 lsp::ParameterInformation {
280 label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
281 documentation: None,
282 },
283 lsp::ParameterInformation {
284 label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
285 documentation: None,
286 },
287 ]),
288 active_parameter: None,
289 },
290 ],
291 active_signature: Some(1),
292 active_parameter: Some(0),
293 };
294 let maybe_markdown = SignatureHelp::new(signature_help, None);
295 assert!(maybe_markdown.is_some());
296
297 let markdown = maybe_markdown.unwrap();
298 let markdown = (markdown.markdown, markdown.highlights);
299 assert_eq!(
300 markdown,
301 (
302 "```\nhoge: String, fuga: bool (+1 overload)".to_string(),
303 vec![
304 (0..12, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
305 (25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
306 ]
307 )
308 );
309 }
310
311 #[test]
312 fn test_create_signature_help_markdown_string_5() {
313 let signature_help = lsp::SignatureHelp {
314 signatures: vec![
315 lsp::SignatureInformation {
316 label: "fn test1(foo: u8, bar: &str)".to_string(),
317 documentation: None,
318 parameters: Some(vec![
319 lsp::ParameterInformation {
320 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
321 documentation: None,
322 },
323 lsp::ParameterInformation {
324 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
325 documentation: None,
326 },
327 ]),
328 active_parameter: None,
329 },
330 lsp::SignatureInformation {
331 label: "fn test2(hoge: String, fuga: bool)".to_string(),
332 documentation: None,
333 parameters: Some(vec![
334 lsp::ParameterInformation {
335 label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
336 documentation: None,
337 },
338 lsp::ParameterInformation {
339 label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
340 documentation: None,
341 },
342 ]),
343 active_parameter: None,
344 },
345 ],
346 active_signature: Some(1),
347 active_parameter: Some(1),
348 };
349 let maybe_markdown = SignatureHelp::new(signature_help, None);
350 assert!(maybe_markdown.is_some());
351
352 let markdown = maybe_markdown.unwrap();
353 let markdown = (markdown.markdown, markdown.highlights);
354 assert_eq!(
355 markdown,
356 (
357 "```\nhoge: String, fuga: bool (+1 overload)".to_string(),
358 vec![
359 (14..24, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
360 (25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
361 ]
362 )
363 );
364 }
365
366 #[test]
367 fn test_create_signature_help_markdown_string_6() {
368 let signature_help = lsp::SignatureHelp {
369 signatures: vec![
370 lsp::SignatureInformation {
371 label: "fn test1(foo: u8, bar: &str)".to_string(),
372 documentation: None,
373 parameters: Some(vec![
374 lsp::ParameterInformation {
375 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
376 documentation: None,
377 },
378 lsp::ParameterInformation {
379 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
380 documentation: None,
381 },
382 ]),
383 active_parameter: None,
384 },
385 lsp::SignatureInformation {
386 label: "fn test2(hoge: String, fuga: bool)".to_string(),
387 documentation: None,
388 parameters: Some(vec![
389 lsp::ParameterInformation {
390 label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
391 documentation: None,
392 },
393 lsp::ParameterInformation {
394 label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
395 documentation: None,
396 },
397 ]),
398 active_parameter: None,
399 },
400 ],
401 active_signature: Some(1),
402 active_parameter: None,
403 };
404 let maybe_markdown = SignatureHelp::new(signature_help, None);
405 assert!(maybe_markdown.is_some());
406
407 let markdown = maybe_markdown.unwrap();
408 let markdown = (markdown.markdown, markdown.highlights);
409 assert_eq!(
410 markdown,
411 (
412 "```\nhoge: String, fuga: bool (+1 overload)".to_string(),
413 vec![(25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)]
414 )
415 );
416 }
417
418 #[test]
419 fn test_create_signature_help_markdown_string_7() {
420 let signature_help = lsp::SignatureHelp {
421 signatures: vec![
422 lsp::SignatureInformation {
423 label: "fn test1(foo: u8, bar: &str)".to_string(),
424 documentation: None,
425 parameters: Some(vec![
426 lsp::ParameterInformation {
427 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
428 documentation: None,
429 },
430 lsp::ParameterInformation {
431 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
432 documentation: None,
433 },
434 ]),
435 active_parameter: None,
436 },
437 lsp::SignatureInformation {
438 label: "fn test2(hoge: String, fuga: bool)".to_string(),
439 documentation: None,
440 parameters: Some(vec![
441 lsp::ParameterInformation {
442 label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
443 documentation: None,
444 },
445 lsp::ParameterInformation {
446 label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
447 documentation: None,
448 },
449 ]),
450 active_parameter: None,
451 },
452 lsp::SignatureInformation {
453 label: "fn test3(one: usize, two: u32)".to_string(),
454 documentation: None,
455 parameters: Some(vec![
456 lsp::ParameterInformation {
457 label: lsp::ParameterLabel::Simple("one: usize".to_string()),
458 documentation: None,
459 },
460 lsp::ParameterInformation {
461 label: lsp::ParameterLabel::Simple("two: u32".to_string()),
462 documentation: None,
463 },
464 ]),
465 active_parameter: None,
466 },
467 ],
468 active_signature: Some(2),
469 active_parameter: Some(1),
470 };
471 let maybe_markdown = SignatureHelp::new(signature_help, None);
472 assert!(maybe_markdown.is_some());
473
474 let markdown = maybe_markdown.unwrap();
475 let markdown = (markdown.markdown, markdown.highlights);
476 assert_eq!(
477 markdown,
478 (
479 "```\none: usize, two: u32 (+2 overload)".to_string(),
480 vec![
481 (12..20, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
482 (21..34, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
483 ]
484 )
485 );
486 }
487
488 #[test]
489 fn test_create_signature_help_markdown_string_8() {
490 let signature_help = lsp::SignatureHelp {
491 signatures: vec![],
492 active_signature: None,
493 active_parameter: None,
494 };
495 let maybe_markdown = SignatureHelp::new(signature_help, None);
496 assert!(maybe_markdown.is_none());
497 }
498
499 #[test]
500 fn test_create_signature_help_markdown_string_9() {
501 let signature_help = lsp::SignatureHelp {
502 signatures: vec![lsp::SignatureInformation {
503 label: "fn test(foo: u8, bar: &str)".to_string(),
504 documentation: None,
505 parameters: Some(vec![
506 lsp::ParameterInformation {
507 label: lsp::ParameterLabel::LabelOffsets([8, 15]),
508 documentation: None,
509 },
510 lsp::ParameterInformation {
511 label: lsp::ParameterLabel::LabelOffsets([17, 26]),
512 documentation: None,
513 },
514 ]),
515 active_parameter: None,
516 }],
517 active_signature: Some(0),
518 active_parameter: Some(0),
519 };
520 let maybe_markdown = SignatureHelp::new(signature_help, None);
521 assert!(maybe_markdown.is_some());
522
523 let markdown = maybe_markdown.unwrap();
524 let markdown = (markdown.markdown, markdown.highlights);
525 assert_eq!(
526 markdown,
527 (
528 "```\nfoo: u8, bar: &str".to_string(),
529 vec![(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
530 )
531 );
532 }
533}