signature_help.rs

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