1use std::{ops::Range, sync::Arc};
  2
  3use gpui::{App, AppContext, Entity, FontWeight, HighlightStyle, SharedString};
  4use language::LanguageRegistry;
  5use lsp::LanguageServerId;
  6use markdown::Markdown;
  7use rpc::proto::{self, documentation};
  8use util::maybe;
  9
 10#[derive(Debug)]
 11pub struct SignatureHelp {
 12    pub active_signature: usize,
 13    pub signatures: Vec<SignatureHelpData>,
 14    pub(super) original_data: lsp::SignatureHelp,
 15}
 16
 17#[derive(Debug, Clone)]
 18pub struct SignatureHelpData {
 19    pub label: SharedString,
 20    pub documentation: Option<Entity<Markdown>>,
 21    pub highlights: Vec<(Range<usize>, HighlightStyle)>,
 22    pub active_parameter: Option<usize>,
 23    pub parameters: Vec<ParameterInfo>,
 24}
 25
 26#[derive(Debug, Clone)]
 27pub struct ParameterInfo {
 28    pub label_range: Option<Range<usize>>,
 29    pub documentation: Option<Entity<Markdown>>,
 30}
 31
 32impl SignatureHelp {
 33    pub fn new(
 34        help: lsp::SignatureHelp,
 35        language_registry: Option<Arc<LanguageRegistry>>,
 36        lang_server_id: Option<LanguageServerId>,
 37        cx: &mut App,
 38    ) -> Option<Self> {
 39        if help.signatures.is_empty() {
 40            return None;
 41        }
 42        let active_signature = help.active_signature.unwrap_or(0) as usize;
 43        let mut signatures = Vec::<SignatureHelpData>::with_capacity(help.signatures.capacity());
 44        for signature in &help.signatures {
 45            let label = SharedString::from(signature.label.clone());
 46            let active_parameter = signature
 47                .active_parameter
 48                .unwrap_or_else(|| help.active_parameter.unwrap_or(0))
 49                as usize;
 50            let mut highlights = Vec::new();
 51            let mut parameter_infos = Vec::new();
 52
 53            if let Some(parameters) = &signature.parameters {
 54                for (index, parameter) in parameters.iter().enumerate() {
 55                    let label_range = match ¶meter.label {
 56                        &lsp::ParameterLabel::LabelOffsets([offset1, offset2]) => {
 57                            maybe!({
 58                                let offset1 = offset1 as usize;
 59                                let offset2 = offset2 as usize;
 60                                if offset1 < offset2 {
 61                                    let mut indices = label.char_indices().scan(
 62                                        0,
 63                                        |utf16_offset_acc, (offset, c)| {
 64                                            let utf16_offset = *utf16_offset_acc;
 65                                            *utf16_offset_acc += c.len_utf16();
 66                                            Some((utf16_offset, offset))
 67                                        },
 68                                    );
 69                                    let (_, offset1) = indices
 70                                        .find(|(utf16_offset, _)| *utf16_offset == offset1)?;
 71                                    let (_, offset2) = indices
 72                                        .find(|(utf16_offset, _)| *utf16_offset == offset2)?;
 73                                    Some(offset1..offset2)
 74                                } else {
 75                                    log::warn!(
 76                                        "language server {lang_server_id:?} produced invalid parameter label range: {offset1:?}..{offset2:?}",
 77                                    );
 78                                    None
 79                                }
 80                            })
 81                        }
 82                        lsp::ParameterLabel::Simple(parameter_label) => {
 83                            if let Some(start) = signature.label.find(parameter_label) {
 84                                Some(start..start + parameter_label.len())
 85                            } else {
 86                                None
 87                            }
 88                        }
 89                    };
 90
 91                    if let Some(label_range) = &label_range
 92                        && index == active_parameter
 93                    {
 94                        highlights.push((
 95                            label_range.clone(),
 96                            HighlightStyle {
 97                                font_weight: Some(FontWeight::EXTRA_BOLD),
 98                                ..HighlightStyle::default()
 99                            },
100                        ));
101                    }
102
103                    let documentation = parameter
104                        .documentation
105                        .as_ref()
106                        .map(|doc| documentation_to_markdown(doc, language_registry.clone(), cx));
107
108                    parameter_infos.push(ParameterInfo {
109                        label_range,
110                        documentation,
111                    });
112                }
113            }
114
115            let documentation = signature
116                .documentation
117                .as_ref()
118                .map(|doc| documentation_to_markdown(doc, language_registry.clone(), cx));
119
120            signatures.push(SignatureHelpData {
121                label,
122                documentation,
123                highlights,
124                active_parameter: Some(active_parameter),
125                parameters: parameter_infos,
126            });
127        }
128        Some(Self {
129            signatures,
130            active_signature,
131            original_data: help,
132        })
133    }
134}
135
136fn documentation_to_markdown(
137    documentation: &lsp::Documentation,
138    language_registry: Option<Arc<LanguageRegistry>>,
139    cx: &mut App,
140) -> Entity<Markdown> {
141    match documentation {
142        lsp::Documentation::String(string) => {
143            cx.new(|cx| Markdown::new_text(SharedString::from(string), cx))
144        }
145        lsp::Documentation::MarkupContent(markup) => match markup.kind {
146            lsp::MarkupKind::PlainText => {
147                cx.new(|cx| Markdown::new_text(SharedString::from(&markup.value), cx))
148            }
149            lsp::MarkupKind::Markdown => cx.new(|cx| {
150                Markdown::new(
151                    SharedString::from(&markup.value),
152                    language_registry,
153                    None,
154                    cx,
155                )
156            }),
157        },
158    }
159}
160
161pub fn lsp_to_proto_signature(lsp_help: lsp::SignatureHelp) -> proto::SignatureHelp {
162    proto::SignatureHelp {
163        signatures: lsp_help
164            .signatures
165            .into_iter()
166            .map(|signature| proto::SignatureInformation {
167                label: signature.label,
168                documentation: signature.documentation.map(lsp_to_proto_documentation),
169                parameters: signature
170                    .parameters
171                    .unwrap_or_default()
172                    .into_iter()
173                    .map(|parameter_info| proto::ParameterInformation {
174                        label: Some(match parameter_info.label {
175                            lsp::ParameterLabel::Simple(label) => {
176                                proto::parameter_information::Label::Simple(label)
177                            }
178                            lsp::ParameterLabel::LabelOffsets(offsets) => {
179                                proto::parameter_information::Label::LabelOffsets(
180                                    proto::LabelOffsets {
181                                        start: offsets[0],
182                                        end: offsets[1],
183                                    },
184                                )
185                            }
186                        }),
187                        documentation: parameter_info.documentation.map(lsp_to_proto_documentation),
188                    })
189                    .collect(),
190                active_parameter: signature.active_parameter,
191            })
192            .collect(),
193        active_signature: lsp_help.active_signature,
194        active_parameter: lsp_help.active_parameter,
195    }
196}
197
198fn lsp_to_proto_documentation(documentation: lsp::Documentation) -> proto::Documentation {
199    proto::Documentation {
200        content: Some(match documentation {
201            lsp::Documentation::String(string) => proto::documentation::Content::Value(string),
202            lsp::Documentation::MarkupContent(content) => {
203                proto::documentation::Content::MarkupContent(proto::MarkupContent {
204                    is_markdown: matches!(content.kind, lsp::MarkupKind::Markdown),
205                    value: content.value,
206                })
207            }
208        }),
209    }
210}
211
212pub fn proto_to_lsp_signature(proto_help: proto::SignatureHelp) -> lsp::SignatureHelp {
213    lsp::SignatureHelp {
214        signatures: proto_help
215            .signatures
216            .into_iter()
217            .map(|signature| lsp::SignatureInformation {
218                label: signature.label,
219                documentation: signature.documentation.and_then(proto_to_lsp_documentation),
220                parameters: Some(
221                    signature
222                        .parameters
223                        .into_iter()
224                        .filter_map(|parameter_info| {
225                            Some(lsp::ParameterInformation {
226                                label: match parameter_info.label? {
227                                    proto::parameter_information::Label::Simple(string) => {
228                                        lsp::ParameterLabel::Simple(string)
229                                    }
230                                    proto::parameter_information::Label::LabelOffsets(offsets) => {
231                                        lsp::ParameterLabel::LabelOffsets([
232                                            offsets.start,
233                                            offsets.end,
234                                        ])
235                                    }
236                                },
237                                documentation: parameter_info
238                                    .documentation
239                                    .and_then(proto_to_lsp_documentation),
240                            })
241                        })
242                        .collect(),
243                ),
244                active_parameter: signature.active_parameter,
245            })
246            .collect(),
247        active_signature: proto_help.active_signature,
248        active_parameter: proto_help.active_parameter,
249    }
250}
251
252fn proto_to_lsp_documentation(documentation: proto::Documentation) -> Option<lsp::Documentation> {
253    {
254        Some(match documentation.content? {
255            documentation::Content::Value(string) => lsp::Documentation::String(string),
256            documentation::Content::MarkupContent(markup) => {
257                lsp::Documentation::MarkupContent(if markup.is_markdown {
258                    lsp::MarkupContent {
259                        kind: lsp::MarkupKind::Markdown,
260                        value: markup.value,
261                    }
262                } else {
263                    lsp::MarkupContent {
264                        kind: lsp::MarkupKind::PlainText,
265                        value: markup.value,
266                    }
267                })
268            }
269        })
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use gpui::{FontWeight, HighlightStyle, SharedString, TestAppContext};
276    use lsp::{Documentation, MarkupContent, MarkupKind};
277
278    use crate::lsp_command::signature_help::SignatureHelp;
279
280    fn current_parameter() -> HighlightStyle {
281        HighlightStyle {
282            font_weight: Some(FontWeight::EXTRA_BOLD),
283            ..Default::default()
284        }
285    }
286
287    #[gpui::test]
288    fn test_create_signature_help_markdown_string_1(cx: &mut TestAppContext) {
289        let signature_help = lsp::SignatureHelp {
290            signatures: vec![lsp::SignatureInformation {
291                label: "fn test(foo: u8, bar: &str)".to_string(),
292                documentation: Some(Documentation::String(
293                    "This is a test documentation".to_string(),
294                )),
295                parameters: Some(vec![
296                    lsp::ParameterInformation {
297                        label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
298                        documentation: None,
299                    },
300                    lsp::ParameterInformation {
301                        label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
302                        documentation: None,
303                    },
304                ]),
305                active_parameter: None,
306            }],
307            active_signature: Some(0),
308            active_parameter: Some(0),
309        };
310        let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
311        assert!(maybe_markdown.is_some());
312
313        let markdown = maybe_markdown.unwrap();
314        let signature = markdown.signatures[markdown.active_signature].clone();
315        let markdown = (signature.label, signature.highlights);
316        assert_eq!(
317            markdown,
318            (
319                SharedString::new("fn test(foo: u8, bar: &str)"),
320                vec![(8..15, current_parameter())]
321            )
322        );
323        assert_eq!(
324            signature
325                .documentation
326                .unwrap()
327                .update(cx, |documentation, _| documentation.source().to_owned()),
328            "This is a test documentation",
329        )
330    }
331
332    #[gpui::test]
333    fn test_create_signature_help_markdown_string_2(cx: &mut TestAppContext) {
334        let signature_help = lsp::SignatureHelp {
335            signatures: vec![lsp::SignatureInformation {
336                label: "fn test(foo: u8, bar: &str)".to_string(),
337                documentation: Some(Documentation::MarkupContent(MarkupContent {
338                    kind: MarkupKind::Markdown,
339                    value: "This is a test documentation".to_string(),
340                })),
341                parameters: Some(vec![
342                    lsp::ParameterInformation {
343                        label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
344                        documentation: None,
345                    },
346                    lsp::ParameterInformation {
347                        label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
348                        documentation: None,
349                    },
350                ]),
351                active_parameter: None,
352            }],
353            active_signature: Some(0),
354            active_parameter: Some(1),
355        };
356        let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
357        assert!(maybe_markdown.is_some());
358
359        let markdown = maybe_markdown.unwrap();
360        let signature = markdown.signatures[markdown.active_signature].clone();
361        let markdown = (signature.label, signature.highlights);
362        assert_eq!(
363            markdown,
364            (
365                SharedString::new("fn test(foo: u8, bar: &str)"),
366                vec![(17..26, current_parameter())]
367            )
368        );
369        assert_eq!(
370            signature
371                .documentation
372                .unwrap()
373                .update(cx, |documentation, _| documentation.source().to_owned()),
374            "This is a test documentation",
375        )
376    }
377
378    #[gpui::test]
379    fn test_create_signature_help_markdown_string_3(cx: &mut TestAppContext) {
380        let signature_help = lsp::SignatureHelp {
381            signatures: vec![
382                lsp::SignatureInformation {
383                    label: "fn test1(foo: u8, bar: &str)".to_string(),
384                    documentation: None,
385                    parameters: Some(vec![
386                        lsp::ParameterInformation {
387                            label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
388                            documentation: None,
389                        },
390                        lsp::ParameterInformation {
391                            label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
392                            documentation: None,
393                        },
394                    ]),
395                    active_parameter: None,
396                },
397                lsp::SignatureInformation {
398                    label: "fn test2(hoge: String, fuga: bool)".to_string(),
399                    documentation: None,
400                    parameters: Some(vec![
401                        lsp::ParameterInformation {
402                            label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
403                            documentation: None,
404                        },
405                        lsp::ParameterInformation {
406                            label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
407                            documentation: None,
408                        },
409                    ]),
410                    active_parameter: None,
411                },
412            ],
413            active_signature: Some(0),
414            active_parameter: Some(0),
415        };
416        let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
417        assert!(maybe_markdown.is_some());
418
419        let markdown = maybe_markdown.unwrap();
420        let signature = markdown.signatures[markdown.active_signature].clone();
421        let markdown = (signature.label, signature.highlights);
422        assert_eq!(
423            markdown,
424            (
425                SharedString::new("fn test1(foo: u8, bar: &str)"),
426                vec![(9..16, current_parameter())]
427            )
428        );
429    }
430
431    #[gpui::test]
432    fn test_create_signature_help_markdown_string_4(cx: &mut TestAppContext) {
433        let signature_help = lsp::SignatureHelp {
434            signatures: vec![
435                lsp::SignatureInformation {
436                    label: "fn test1(foo: u8, bar: &str)".to_string(),
437                    documentation: None,
438                    parameters: Some(vec![
439                        lsp::ParameterInformation {
440                            label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
441                            documentation: None,
442                        },
443                        lsp::ParameterInformation {
444                            label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
445                            documentation: None,
446                        },
447                    ]),
448                    active_parameter: None,
449                },
450                lsp::SignatureInformation {
451                    label: "fn test2(hoge: String, fuga: bool)".to_string(),
452                    documentation: None,
453                    parameters: Some(vec![
454                        lsp::ParameterInformation {
455                            label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
456                            documentation: None,
457                        },
458                        lsp::ParameterInformation {
459                            label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
460                            documentation: None,
461                        },
462                    ]),
463                    active_parameter: None,
464                },
465            ],
466            active_signature: Some(1),
467            active_parameter: Some(0),
468        };
469        let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
470        assert!(maybe_markdown.is_some());
471
472        let markdown = maybe_markdown.unwrap();
473        let signature = markdown.signatures[markdown.active_signature].clone();
474        let markdown = (signature.label, signature.highlights);
475        assert_eq!(
476            markdown,
477            (
478                SharedString::new("fn test2(hoge: String, fuga: bool)"),
479                vec![(9..21, current_parameter())]
480            )
481        );
482    }
483
484    #[gpui::test]
485    fn test_create_signature_help_markdown_string_5(cx: &mut TestAppContext) {
486        let signature_help = lsp::SignatureHelp {
487            signatures: vec![
488                lsp::SignatureInformation {
489                    label: "fn test1(foo: u8, bar: &str)".to_string(),
490                    documentation: None,
491                    parameters: Some(vec![
492                        lsp::ParameterInformation {
493                            label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
494                            documentation: None,
495                        },
496                        lsp::ParameterInformation {
497                            label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
498                            documentation: None,
499                        },
500                    ]),
501                    active_parameter: None,
502                },
503                lsp::SignatureInformation {
504                    label: "fn test2(hoge: String, fuga: bool)".to_string(),
505                    documentation: None,
506                    parameters: Some(vec![
507                        lsp::ParameterInformation {
508                            label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
509                            documentation: None,
510                        },
511                        lsp::ParameterInformation {
512                            label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
513                            documentation: None,
514                        },
515                    ]),
516                    active_parameter: None,
517                },
518            ],
519            active_signature: Some(1),
520            active_parameter: Some(1),
521        };
522        let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
523        assert!(maybe_markdown.is_some());
524
525        let markdown = maybe_markdown.unwrap();
526        let signature = markdown.signatures[markdown.active_signature].clone();
527        let markdown = (signature.label, signature.highlights);
528        assert_eq!(
529            markdown,
530            (
531                SharedString::new("fn test2(hoge: String, fuga: bool)"),
532                vec![(23..33, current_parameter())]
533            )
534        );
535    }
536
537    #[gpui::test]
538    fn test_create_signature_help_markdown_string_6(cx: &mut TestAppContext) {
539        let signature_help = lsp::SignatureHelp {
540            signatures: vec![
541                lsp::SignatureInformation {
542                    label: "fn test1(foo: u8, bar: &str)".to_string(),
543                    documentation: None,
544                    parameters: Some(vec![
545                        lsp::ParameterInformation {
546                            label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
547                            documentation: None,
548                        },
549                        lsp::ParameterInformation {
550                            label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
551                            documentation: None,
552                        },
553                    ]),
554                    active_parameter: None,
555                },
556                lsp::SignatureInformation {
557                    label: "fn test2(hoge: String, fuga: bool)".to_string(),
558                    documentation: None,
559                    parameters: Some(vec![
560                        lsp::ParameterInformation {
561                            label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
562                            documentation: None,
563                        },
564                        lsp::ParameterInformation {
565                            label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
566                            documentation: None,
567                        },
568                    ]),
569                    active_parameter: None,
570                },
571            ],
572            active_signature: Some(1),
573            active_parameter: None,
574        };
575        let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
576        assert!(maybe_markdown.is_some());
577
578        let markdown = maybe_markdown.unwrap();
579        let signature = markdown.signatures[markdown.active_signature].clone();
580        let markdown = (signature.label, signature.highlights);
581        assert_eq!(
582            markdown,
583            (
584                SharedString::new("fn test2(hoge: String, fuga: bool)"),
585                vec![(9..21, current_parameter())]
586            )
587        );
588    }
589
590    #[gpui::test]
591    fn test_create_signature_help_markdown_string_7(cx: &mut TestAppContext) {
592        let signature_help = lsp::SignatureHelp {
593            signatures: vec![
594                lsp::SignatureInformation {
595                    label: "fn test1(foo: u8, bar: &str)".to_string(),
596                    documentation: None,
597                    parameters: Some(vec![
598                        lsp::ParameterInformation {
599                            label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
600                            documentation: None,
601                        },
602                        lsp::ParameterInformation {
603                            label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
604                            documentation: None,
605                        },
606                    ]),
607                    active_parameter: None,
608                },
609                lsp::SignatureInformation {
610                    label: "fn test2(hoge: String, fuga: bool)".to_string(),
611                    documentation: None,
612                    parameters: Some(vec![
613                        lsp::ParameterInformation {
614                            label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
615                            documentation: None,
616                        },
617                        lsp::ParameterInformation {
618                            label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
619                            documentation: None,
620                        },
621                    ]),
622                    active_parameter: None,
623                },
624                lsp::SignatureInformation {
625                    label: "fn test3(one: usize, two: u32)".to_string(),
626                    documentation: None,
627                    parameters: Some(vec![
628                        lsp::ParameterInformation {
629                            label: lsp::ParameterLabel::Simple("one: usize".to_string()),
630                            documentation: None,
631                        },
632                        lsp::ParameterInformation {
633                            label: lsp::ParameterLabel::Simple("two: u32".to_string()),
634                            documentation: None,
635                        },
636                    ]),
637                    active_parameter: None,
638                },
639            ],
640            active_signature: Some(2),
641            active_parameter: Some(1),
642        };
643        let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
644        assert!(maybe_markdown.is_some());
645
646        let markdown = maybe_markdown.unwrap();
647        let signature = markdown.signatures[markdown.active_signature].clone();
648        let markdown = (signature.label, signature.highlights);
649        assert_eq!(
650            markdown,
651            (
652                SharedString::new("fn test3(one: usize, two: u32)"),
653                vec![(21..29, current_parameter())]
654            )
655        );
656    }
657
658    #[gpui::test]
659    fn test_create_signature_help_markdown_string_8(cx: &mut TestAppContext) {
660        let signature_help = lsp::SignatureHelp {
661            signatures: vec![],
662            active_signature: None,
663            active_parameter: None,
664        };
665        let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
666        assert!(maybe_markdown.is_none());
667    }
668
669    #[gpui::test]
670    fn test_create_signature_help_markdown_string_9(cx: &mut TestAppContext) {
671        let signature_help = lsp::SignatureHelp {
672            signatures: vec![lsp::SignatureInformation {
673                label: "fn test(foo: u8, bar: &str)".to_string(),
674                documentation: None,
675                parameters: Some(vec![
676                    lsp::ParameterInformation {
677                        label: lsp::ParameterLabel::LabelOffsets([8, 15]),
678                        documentation: None,
679                    },
680                    lsp::ParameterInformation {
681                        label: lsp::ParameterLabel::LabelOffsets([17, 26]),
682                        documentation: None,
683                    },
684                ]),
685                active_parameter: None,
686            }],
687            active_signature: Some(0),
688            active_parameter: Some(0),
689        };
690        let maybe_markdown = cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
691        assert!(maybe_markdown.is_some());
692
693        let markdown = maybe_markdown.unwrap();
694        let signature = markdown.signatures[markdown.active_signature].clone();
695        let markdown = (signature.label, signature.highlights);
696        assert_eq!(
697            markdown,
698            (
699                SharedString::new("fn test(foo: u8, bar: &str)"),
700                vec![(8..15, current_parameter())]
701            )
702        );
703    }
704
705    #[gpui::test]
706    fn test_parameter_documentation(cx: &mut TestAppContext) {
707        let signature_help = lsp::SignatureHelp {
708            signatures: vec![lsp::SignatureInformation {
709                label: "fn test(foo: u8, bar: &str)".to_string(),
710                documentation: Some(Documentation::String(
711                    "This is a test documentation".to_string(),
712                )),
713                parameters: Some(vec![
714                    lsp::ParameterInformation {
715                        label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
716                        documentation: Some(Documentation::String("The foo parameter".to_string())),
717                    },
718                    lsp::ParameterInformation {
719                        label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
720                        documentation: Some(Documentation::String("The bar parameter".to_string())),
721                    },
722                ]),
723                active_parameter: None,
724            }],
725            active_signature: Some(0),
726            active_parameter: Some(0),
727        };
728        let maybe_signature_help =
729            cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
730        assert!(maybe_signature_help.is_some());
731
732        let signature_help = maybe_signature_help.unwrap();
733        let signature = &signature_help.signatures[signature_help.active_signature];
734
735        // Check that parameter documentation is extracted
736        assert_eq!(signature.parameters.len(), 2);
737        assert_eq!(
738            signature.parameters[0]
739                .documentation
740                .as_ref()
741                .unwrap()
742                .update(cx, |documentation, _| documentation.source().to_owned()),
743            "The foo parameter",
744        );
745        assert_eq!(
746            signature.parameters[1]
747                .documentation
748                .as_ref()
749                .unwrap()
750                .update(cx, |documentation, _| documentation.source().to_owned()),
751            "The bar parameter",
752        );
753
754        // Check that the active parameter is correct
755        assert_eq!(signature.active_parameter, Some(0));
756    }
757
758    #[gpui::test]
759    fn test_create_signature_help_implements_utf16_spec(cx: &mut TestAppContext) {
760        let signature_help = lsp::SignatureHelp {
761            signatures: vec![lsp::SignatureInformation {
762                label: "fn test(🦀: u8, 🦀: &str)".to_string(),
763                documentation: None,
764                parameters: Some(vec![
765                    lsp::ParameterInformation {
766                        label: lsp::ParameterLabel::LabelOffsets([8, 10]),
767                        documentation: None,
768                    },
769                    lsp::ParameterInformation {
770                        label: lsp::ParameterLabel::LabelOffsets([16, 18]),
771                        documentation: None,
772                    },
773                ]),
774                active_parameter: None,
775            }],
776            active_signature: Some(0),
777            active_parameter: Some(0),
778        };
779        let signature_help = cx.update(|cx| SignatureHelp::new(signature_help, None, None, cx));
780        assert!(signature_help.is_some());
781
782        let markdown = signature_help.unwrap();
783        let signature = markdown.signatures[markdown.active_signature].clone();
784        let markdown = (signature.label, signature.highlights);
785        assert_eq!(
786            markdown,
787            (
788                SharedString::new("fn test(🦀: u8, 🦀: &str)"),
789                vec![(8..12, current_parameter())]
790            )
791        );
792    }
793}