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().0.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.documentation.map(lsp_to_proto_documentation),
121 parameters: signature
122 .parameters
123 .unwrap_or_default()
124 .into_iter()
125 .map(|parameter_info| proto::ParameterInformation {
126 label: Some(match parameter_info.label {
127 lsp::ParameterLabel::Simple(label) => {
128 proto::parameter_information::Label::Simple(label)
129 }
130 lsp::ParameterLabel::LabelOffsets(offsets) => {
131 proto::parameter_information::Label::LabelOffsets(
132 proto::LabelOffsets {
133 start: offsets[0],
134 end: offsets[1],
135 },
136 )
137 }
138 }),
139 documentation: parameter_info.documentation.map(lsp_to_proto_documentation),
140 })
141 .collect(),
142 active_parameter: signature.active_parameter,
143 })
144 .collect(),
145 active_signature: lsp_help.active_signature,
146 active_parameter: lsp_help.active_parameter,
147 }
148}
149
150fn lsp_to_proto_documentation(documentation: lsp::Documentation) -> proto::Documentation {
151 proto::Documentation {
152 content: Some(match documentation {
153 lsp::Documentation::String(string) => proto::documentation::Content::Value(string),
154 lsp::Documentation::MarkupContent(content) => {
155 proto::documentation::Content::MarkupContent(proto::MarkupContent {
156 is_markdown: matches!(content.kind, lsp::MarkupKind::Markdown),
157 value: content.value,
158 })
159 }
160 }),
161 }
162}
163
164pub fn proto_to_lsp_signature(proto_help: proto::SignatureHelp) -> lsp::SignatureHelp {
165 lsp::SignatureHelp {
166 signatures: proto_help
167 .signatures
168 .into_iter()
169 .map(|signature| lsp::SignatureInformation {
170 label: signature.label,
171 documentation: signature.documentation.and_then(proto_to_lsp_documentation),
172 parameters: Some(
173 signature
174 .parameters
175 .into_iter()
176 .filter_map(|parameter_info| {
177 Some(lsp::ParameterInformation {
178 label: match parameter_info.label? {
179 proto::parameter_information::Label::Simple(string) => {
180 lsp::ParameterLabel::Simple(string)
181 }
182 proto::parameter_information::Label::LabelOffsets(offsets) => {
183 lsp::ParameterLabel::LabelOffsets([
184 offsets.start,
185 offsets.end,
186 ])
187 }
188 },
189 documentation: parameter_info
190 .documentation
191 .and_then(proto_to_lsp_documentation),
192 })
193 })
194 .collect(),
195 ),
196 active_parameter: signature.active_parameter,
197 })
198 .collect(),
199 active_signature: proto_help.active_signature,
200 active_parameter: proto_help.active_parameter,
201 }
202}
203
204fn proto_to_lsp_documentation(documentation: proto::Documentation) -> Option<lsp::Documentation> {
205 {
206 Some(match documentation.content? {
207 documentation::Content::Value(string) => lsp::Documentation::String(string),
208 documentation::Content::MarkupContent(markup) => {
209 lsp::Documentation::MarkupContent(if markup.is_markdown {
210 lsp::MarkupContent {
211 kind: lsp::MarkupKind::Markdown,
212 value: markup.value,
213 }
214 } else {
215 lsp::MarkupContent {
216 kind: lsp::MarkupKind::PlainText,
217 value: markup.value,
218 }
219 })
220 }
221 })
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use crate::lsp_command::signature_help::{
228 SignatureHelp, SIGNATURE_HELP_HIGHLIGHT_CURRENT, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD,
229 };
230
231 #[test]
232 fn test_create_signature_help_markdown_string_1() {
233 let signature_help = lsp::SignatureHelp {
234 signatures: vec![lsp::SignatureInformation {
235 label: "fn test(foo: u8, bar: &str)".to_string(),
236 documentation: None,
237 parameters: Some(vec![
238 lsp::ParameterInformation {
239 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
240 documentation: None,
241 },
242 lsp::ParameterInformation {
243 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
244 documentation: None,
245 },
246 ]),
247 active_parameter: None,
248 }],
249 active_signature: Some(0),
250 active_parameter: Some(0),
251 };
252 let maybe_markdown = SignatureHelp::new(signature_help, None);
253 assert!(maybe_markdown.is_some());
254
255 let markdown = maybe_markdown.unwrap();
256 let markdown = (markdown.markdown, markdown.highlights);
257 assert_eq!(
258 markdown,
259 (
260 "```\nfoo: u8, bar: &str".to_string(),
261 vec![(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
262 )
263 );
264 }
265
266 #[test]
267 fn test_create_signature_help_markdown_string_2() {
268 let signature_help = lsp::SignatureHelp {
269 signatures: vec![lsp::SignatureInformation {
270 label: "fn test(foo: u8, bar: &str)".to_string(),
271 documentation: None,
272 parameters: Some(vec![
273 lsp::ParameterInformation {
274 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
275 documentation: None,
276 },
277 lsp::ParameterInformation {
278 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
279 documentation: None,
280 },
281 ]),
282 active_parameter: None,
283 }],
284 active_signature: Some(0),
285 active_parameter: Some(1),
286 };
287 let maybe_markdown = SignatureHelp::new(signature_help, None);
288 assert!(maybe_markdown.is_some());
289
290 let markdown = maybe_markdown.unwrap();
291 let markdown = (markdown.markdown, markdown.highlights);
292 assert_eq!(
293 markdown,
294 (
295 "```\nfoo: u8, bar: &str".to_string(),
296 vec![(9..18, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
297 )
298 );
299 }
300
301 #[test]
302 fn test_create_signature_help_markdown_string_3() {
303 let signature_help = lsp::SignatureHelp {
304 signatures: vec![
305 lsp::SignatureInformation {
306 label: "fn test1(foo: u8, bar: &str)".to_string(),
307 documentation: None,
308 parameters: Some(vec![
309 lsp::ParameterInformation {
310 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
311 documentation: None,
312 },
313 lsp::ParameterInformation {
314 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
315 documentation: None,
316 },
317 ]),
318 active_parameter: None,
319 },
320 lsp::SignatureInformation {
321 label: "fn test2(hoge: String, fuga: bool)".to_string(),
322 documentation: None,
323 parameters: Some(vec![
324 lsp::ParameterInformation {
325 label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
326 documentation: None,
327 },
328 lsp::ParameterInformation {
329 label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
330 documentation: None,
331 },
332 ]),
333 active_parameter: None,
334 },
335 ],
336 active_signature: Some(0),
337 active_parameter: Some(0),
338 };
339 let maybe_markdown = SignatureHelp::new(signature_help, None);
340 assert!(maybe_markdown.is_some());
341
342 let markdown = maybe_markdown.unwrap();
343 let markdown = (markdown.markdown, markdown.highlights);
344 assert_eq!(
345 markdown,
346 (
347 "```\nfoo: u8, bar: &str (+1 overload)".to_string(),
348 vec![
349 (0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
350 (19..32, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
351 ]
352 )
353 );
354 }
355
356 #[test]
357 fn test_create_signature_help_markdown_string_4() {
358 let signature_help = lsp::SignatureHelp {
359 signatures: vec![
360 lsp::SignatureInformation {
361 label: "fn test1(foo: u8, bar: &str)".to_string(),
362 documentation: None,
363 parameters: Some(vec![
364 lsp::ParameterInformation {
365 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
366 documentation: None,
367 },
368 lsp::ParameterInformation {
369 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
370 documentation: None,
371 },
372 ]),
373 active_parameter: None,
374 },
375 lsp::SignatureInformation {
376 label: "fn test2(hoge: String, fuga: bool)".to_string(),
377 documentation: None,
378 parameters: Some(vec![
379 lsp::ParameterInformation {
380 label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
381 documentation: None,
382 },
383 lsp::ParameterInformation {
384 label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
385 documentation: None,
386 },
387 ]),
388 active_parameter: None,
389 },
390 ],
391 active_signature: Some(1),
392 active_parameter: Some(0),
393 };
394 let maybe_markdown = SignatureHelp::new(signature_help, None);
395 assert!(maybe_markdown.is_some());
396
397 let markdown = maybe_markdown.unwrap();
398 let markdown = (markdown.markdown, markdown.highlights);
399 assert_eq!(
400 markdown,
401 (
402 "```\nhoge: String, fuga: bool (+1 overload)".to_string(),
403 vec![
404 (0..12, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
405 (25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
406 ]
407 )
408 );
409 }
410
411 #[test]
412 fn test_create_signature_help_markdown_string_5() {
413 let signature_help = lsp::SignatureHelp {
414 signatures: vec![
415 lsp::SignatureInformation {
416 label: "fn test1(foo: u8, bar: &str)".to_string(),
417 documentation: None,
418 parameters: Some(vec![
419 lsp::ParameterInformation {
420 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
421 documentation: None,
422 },
423 lsp::ParameterInformation {
424 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
425 documentation: None,
426 },
427 ]),
428 active_parameter: None,
429 },
430 lsp::SignatureInformation {
431 label: "fn test2(hoge: String, fuga: bool)".to_string(),
432 documentation: None,
433 parameters: Some(vec![
434 lsp::ParameterInformation {
435 label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
436 documentation: None,
437 },
438 lsp::ParameterInformation {
439 label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
440 documentation: None,
441 },
442 ]),
443 active_parameter: None,
444 },
445 ],
446 active_signature: Some(1),
447 active_parameter: Some(1),
448 };
449 let maybe_markdown = SignatureHelp::new(signature_help, None);
450 assert!(maybe_markdown.is_some());
451
452 let markdown = maybe_markdown.unwrap();
453 let markdown = (markdown.markdown, markdown.highlights);
454 assert_eq!(
455 markdown,
456 (
457 "```\nhoge: String, fuga: bool (+1 overload)".to_string(),
458 vec![
459 (14..24, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
460 (25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
461 ]
462 )
463 );
464 }
465
466 #[test]
467 fn test_create_signature_help_markdown_string_6() {
468 let signature_help = lsp::SignatureHelp {
469 signatures: vec![
470 lsp::SignatureInformation {
471 label: "fn test1(foo: u8, bar: &str)".to_string(),
472 documentation: None,
473 parameters: Some(vec![
474 lsp::ParameterInformation {
475 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
476 documentation: None,
477 },
478 lsp::ParameterInformation {
479 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
480 documentation: None,
481 },
482 ]),
483 active_parameter: None,
484 },
485 lsp::SignatureInformation {
486 label: "fn test2(hoge: String, fuga: bool)".to_string(),
487 documentation: None,
488 parameters: Some(vec![
489 lsp::ParameterInformation {
490 label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
491 documentation: None,
492 },
493 lsp::ParameterInformation {
494 label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
495 documentation: None,
496 },
497 ]),
498 active_parameter: None,
499 },
500 ],
501 active_signature: Some(1),
502 active_parameter: None,
503 };
504 let maybe_markdown = SignatureHelp::new(signature_help, None);
505 assert!(maybe_markdown.is_some());
506
507 let markdown = maybe_markdown.unwrap();
508 let markdown = (markdown.markdown, markdown.highlights);
509 assert_eq!(
510 markdown,
511 (
512 "```\nhoge: String, fuga: bool (+1 overload)".to_string(),
513 vec![(25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)]
514 )
515 );
516 }
517
518 #[test]
519 fn test_create_signature_help_markdown_string_7() {
520 let signature_help = lsp::SignatureHelp {
521 signatures: vec![
522 lsp::SignatureInformation {
523 label: "fn test1(foo: u8, bar: &str)".to_string(),
524 documentation: None,
525 parameters: Some(vec![
526 lsp::ParameterInformation {
527 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
528 documentation: None,
529 },
530 lsp::ParameterInformation {
531 label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
532 documentation: None,
533 },
534 ]),
535 active_parameter: None,
536 },
537 lsp::SignatureInformation {
538 label: "fn test2(hoge: String, fuga: bool)".to_string(),
539 documentation: None,
540 parameters: Some(vec![
541 lsp::ParameterInformation {
542 label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
543 documentation: None,
544 },
545 lsp::ParameterInformation {
546 label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
547 documentation: None,
548 },
549 ]),
550 active_parameter: None,
551 },
552 lsp::SignatureInformation {
553 label: "fn test3(one: usize, two: u32)".to_string(),
554 documentation: None,
555 parameters: Some(vec![
556 lsp::ParameterInformation {
557 label: lsp::ParameterLabel::Simple("one: usize".to_string()),
558 documentation: None,
559 },
560 lsp::ParameterInformation {
561 label: lsp::ParameterLabel::Simple("two: u32".to_string()),
562 documentation: None,
563 },
564 ]),
565 active_parameter: None,
566 },
567 ],
568 active_signature: Some(2),
569 active_parameter: Some(1),
570 };
571 let maybe_markdown = SignatureHelp::new(signature_help, None);
572 assert!(maybe_markdown.is_some());
573
574 let markdown = maybe_markdown.unwrap();
575 let markdown = (markdown.markdown, markdown.highlights);
576 assert_eq!(
577 markdown,
578 (
579 "```\none: usize, two: u32 (+2 overload)".to_string(),
580 vec![
581 (12..20, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
582 (21..34, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
583 ]
584 )
585 );
586 }
587
588 #[test]
589 fn test_create_signature_help_markdown_string_8() {
590 let signature_help = lsp::SignatureHelp {
591 signatures: vec![],
592 active_signature: None,
593 active_parameter: None,
594 };
595 let maybe_markdown = SignatureHelp::new(signature_help, None);
596 assert!(maybe_markdown.is_none());
597 }
598
599 #[test]
600 fn test_create_signature_help_markdown_string_9() {
601 let signature_help = lsp::SignatureHelp {
602 signatures: vec![lsp::SignatureInformation {
603 label: "fn test(foo: u8, bar: &str)".to_string(),
604 documentation: None,
605 parameters: Some(vec![
606 lsp::ParameterInformation {
607 label: lsp::ParameterLabel::LabelOffsets([8, 15]),
608 documentation: None,
609 },
610 lsp::ParameterInformation {
611 label: lsp::ParameterLabel::LabelOffsets([17, 26]),
612 documentation: None,
613 },
614 ]),
615 active_parameter: None,
616 }],
617 active_signature: Some(0),
618 active_parameter: Some(0),
619 };
620 let maybe_markdown = SignatureHelp::new(signature_help, None);
621 assert!(maybe_markdown.is_some());
622
623 let markdown = maybe_markdown.unwrap();
624 let markdown = (markdown.markdown, markdown.highlights);
625 assert_eq!(
626 markdown,
627 (
628 "```\nfoo: u8, bar: &str".to_string(),
629 vec![(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
630 )
631 );
632 }
633}