signature_help.rs

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