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}