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