Skip to content

Commit 53267ee

Browse files
Only Display Summary on Hover (#449)
1 parent 19fb576 commit 53267ee

File tree

9 files changed

+242
-51
lines changed

9 files changed

+242
-51
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ num-bigint = "0.4.3"
4141
num-complex = "0.4"
4242
num-traits = "0.2.15"
4343
indenter = "0.2"
44+
regex-lite = "0.1.0"
4445
serde = "1.0"
4546
serde-wasm-bindgen = "0.5"
4647
wasm-bindgen = "0.2.84"

language_service/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ log = { workspace = true }
1717
miette = { workspace = true }
1818
qsc = { path = "../compiler/qsc" }
1919
enum-iterator = { workspace = true }
20+
regex-lite = { workspace = true }

language_service/src/hover.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::display::CodeDisplay;
88
use crate::qsc_utils::{find_item, map_offset, span_contains, Compilation};
99
use qsc::ast::visit::{walk_callable_decl, walk_expr, walk_pat, walk_ty_def, Visitor};
1010
use qsc::{ast, hir, resolve};
11+
use regex_lite::Regex;
1112
use std::fmt::Display;
1213
use std::rc::Rc;
1314

@@ -23,6 +24,10 @@ pub struct Span {
2324
pub end: u32,
2425
}
2526

27+
struct Documentation {
28+
summary: String,
29+
}
30+
2631
pub(crate) fn get_hover(
2732
compilation: &Compilation,
2833
source_name: &str,
@@ -188,18 +193,36 @@ impl Visitor<'_> for HoverVisitor<'_> {
188193
}
189194

190195
fn markdown_with_doc(doc: &Rc<str>, code: impl Display) -> String {
191-
if doc.is_empty() {
196+
let parsed_doc = parse_doc(doc);
197+
if parsed_doc.summary.is_empty() {
192198
markdown_fenced_block(code)
193199
} else {
194200
format!(
195201
"{}
196202
{}",
197-
doc,
203+
parsed_doc.summary,
198204
markdown_fenced_block(code)
199205
)
200206
}
201207
}
202208

209+
fn parse_doc(doc: &str) -> Documentation {
210+
let re = Regex::new(r"(?mi)(?:^# Summary$)([\s\S]*?)(?:(^# .*)|\z)").expect("Invalid regex");
211+
let summary = match re.captures(doc) {
212+
Some(captures) => {
213+
let capture = captures
214+
.get(1)
215+
.expect("Didn't find the capture for the given regex");
216+
capture.as_str()
217+
}
218+
None => doc,
219+
}
220+
.trim()
221+
.to_string();
222+
223+
Documentation { summary }
224+
}
225+
203226
fn markdown_fenced_block(code: impl Display) -> String {
204227
format!(
205228
"```qsharp

language_service/src/hover/tests.rs

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@ fn hover_callable_unit_types() {
4040
check(
4141
indoc! {r#"
4242
namespace Test {
43-
/// Doc comment!
43+
/// Doc comment
44+
/// with multiple lines!
4445
operation ◉B↘ar◉() : Unit {}
4546
}
4647
"#},
4748
&expect![[r#"
48-
"Doc comment!\n```qsharp\noperation Bar Unit => Unit\n```\n"
49+
"Doc comment\nwith multiple lines!\n```qsharp\noperation Bar Unit => Unit\n```\n"
4950
"#]],
5051
);
5152
}
@@ -516,3 +517,171 @@ fn hover_foreign_call_functors() {
516517
"#]],
517518
);
518519
}
520+
521+
#[test]
522+
fn hover_callable_summary() {
523+
check(
524+
indoc! {r#"
525+
namespace Test {
526+
/// # Summary
527+
/// This is a
528+
/// multi-line summary!
529+
operation ◉F↘oo◉() : Unit {}
530+
}
531+
"#},
532+
&expect![[r#"
533+
"This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n"
534+
"#]],
535+
);
536+
}
537+
538+
#[test]
539+
fn hover_callable_summary_stuff_before() {
540+
check(
541+
indoc! {r#"
542+
namespace Test {
543+
/// not the summary
544+
/// # Summary
545+
/// This is a
546+
/// multi-line summary!
547+
operation ◉F↘oo◉() : Unit {}
548+
}
549+
"#},
550+
&expect![[r#"
551+
"This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n"
552+
"#]],
553+
);
554+
}
555+
556+
#[test]
557+
fn hover_callable_summary_other_header_before() {
558+
check(
559+
indoc! {r#"
560+
namespace Test {
561+
/// # Not The Summary
562+
/// This stuff is not the summary.
563+
/// # Summary
564+
/// This is a
565+
/// multi-line summary!
566+
operation ◉F↘oo◉() : Unit {}
567+
}
568+
"#},
569+
&expect![[r#"
570+
"This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n"
571+
"#]],
572+
);
573+
}
574+
575+
#[test]
576+
fn hover_callable_summary_other_header_after() {
577+
check(
578+
indoc! {r#"
579+
namespace Test {
580+
/// # Summary
581+
/// This is a
582+
/// multi-line summary!
583+
/// # Not The Summary
584+
/// This stuff is not the summary.
585+
operation ◉F↘oo◉() : Unit {}
586+
}
587+
"#},
588+
&expect![[r#"
589+
"This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n"
590+
"#]],
591+
);
592+
}
593+
594+
#[test]
595+
fn hover_callable_summary_other_headers() {
596+
check(
597+
indoc! {r#"
598+
namespace Test {
599+
/// # Not The Summary
600+
/// This stuff is not the summary.
601+
/// # Summary
602+
/// This is a
603+
/// multi-line summary!
604+
/// # Also Not The Summary
605+
/// This stuff is also not the summary.
606+
operation ◉F↘oo◉() : Unit {}
607+
}
608+
"#},
609+
&expect![[r#"
610+
"This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n"
611+
"#]],
612+
);
613+
}
614+
615+
#[test]
616+
fn hover_callable_headers_but_no_summary() {
617+
check(
618+
indoc! {r#"
619+
namespace Test {
620+
/// # Not The Summary
621+
/// This stuff is not the summary.
622+
/// # Also Not The Summary
623+
/// This stuff is also not the summary.
624+
operation ◉F↘oo◉() : Unit {}
625+
}
626+
"#},
627+
&expect![[r##"
628+
"# Not The Summary\nThis stuff is not the summary.\n# Also Not The Summary\nThis stuff is also not the summary.\n```qsharp\noperation Foo Unit => Unit\n```\n"
629+
"##]],
630+
);
631+
}
632+
633+
#[test]
634+
fn hover_callable_summary_only_header_matches() {
635+
check(
636+
indoc! {r#"
637+
namespace Test {
638+
/// # Not The Summary
639+
/// This stuff is not the # Summary.
640+
/// # Summary
641+
/// This is a
642+
/// multi-line # Summary!
643+
/// # Also Not The Summary
644+
/// This stuff is also not the # Summary.
645+
operation ◉F↘oo◉() : Unit {}
646+
}
647+
"#},
648+
&expect![[r#"
649+
"This is a\nmulti-line # Summary!\n```qsharp\noperation Foo Unit => Unit\n```\n"
650+
"#]],
651+
);
652+
}
653+
654+
#[test]
655+
fn hover_callable_summary_successive_headers() {
656+
check(
657+
indoc! {r#"
658+
namespace Test {
659+
/// # Not The Summary
660+
/// # Summary
661+
/// This is a
662+
/// multi-line summary!
663+
operation ◉F↘oo◉() : Unit {}
664+
}
665+
"#},
666+
&expect![[r#"
667+
"This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n"
668+
"#]],
669+
);
670+
}
671+
672+
#[test]
673+
fn hover_callable_empty_summary() {
674+
check(
675+
indoc! {r#"
676+
namespace Test {
677+
/// # Not The Summary
678+
/// # Summary
679+
/// # Also Not The Summary
680+
operation ◉F↘oo◉() : Unit {}
681+
}
682+
"#},
683+
&expect![[r##"
684+
"```qsharp\noperation Foo Unit => Unit\n```\n"
685+
"##]],
686+
);
687+
}

language_service/src/qsc_utils.rs

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use qsc::hir::visit::Visitor;
5-
use qsc::hir::{Item, ItemId, LocalItemId, PackageId};
4+
use qsc::hir::{Item, ItemId, PackageId};
65
use qsc::{
76
compile::{self, Error},
87
PackageStore, SourceMap,
@@ -46,32 +45,15 @@ pub(crate) fn map_offset(source_map: &SourceMap, source_name: &str, source_offse
4645
}
4746

4847
pub(crate) fn find_item<'a>(compilation: &'a Compilation, id: &ItemId) -> Option<&'a Item> {
49-
let mut finder_pass = FindItem {
50-
id: &id.item,
51-
item: None,
52-
};
5348
let package = if let Some(package_id) = id.package {
54-
&compilation
55-
.package_store
56-
.get(package_id)
57-
.unwrap_or_else(|| panic!("bad package id: {package_id}"))
58-
.package
49+
match &compilation.package_store.get(package_id) {
50+
Some(compilation) => &compilation.package,
51+
None => {
52+
return None;
53+
}
54+
}
5955
} else {
6056
&compilation.unit.package
6157
};
62-
finder_pass.visit_package(package);
63-
finder_pass.item
64-
}
65-
66-
struct FindItem<'a, 'b> {
67-
pub id: &'a LocalItemId,
68-
pub item: Option<&'b Item>,
69-
}
70-
71-
impl<'a, 'b> Visitor<'b> for FindItem<'a, 'b> {
72-
fn visit_item(&mut self, item: &'b Item) {
73-
if item.id == *self.id {
74-
self.item = Some(item);
75-
}
76-
}
58+
package.items.get(id.item)
7759
}

library/std/arithmetic.qs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ namespace Microsoft.Quantum.Arithmetic {
122122

123123
/// # Summary
124124
/// Reversible, in-place ripple-carry addition of two integers.
125+
///
126+
/// # Description
125127
/// Given two $n$-bit integers encoded in LittleEndian registers `xs` and `ys`,
126128
/// and a qubit carry, the operation computes the sum of the two integers
127129
/// where the $n$ least significant bits of the result are held in `ys` and
@@ -274,7 +276,7 @@ namespace Microsoft.Quantum.Arithmetic {
274276
Fact(Length(xs) > 0, "Array should not be empty.");
275277

276278

277-
let nQubits = Length(xs);
279+
let nQubits = Length(xs);
278280
for idx in 0..nQubits - 2 {
279281
CCNOT(xs[idx], ys[idx], xs[idx+1]);
280282
}

0 commit comments

Comments
 (0)