From 2a82080884d59306a1efa05fe57742d4802f7c1b Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Mon, 10 Jul 2023 14:59:41 -0700 Subject: [PATCH 01/17] only display summary on hover, if one is present --- Cargo.lock | 25 +++-- language_service/Cargo.toml | 1 + language_service/src/hover.rs | 23 ++++- language_service/src/hover/tests.rs | 138 +++++++++++++++++++++++++++- 4 files changed, 177 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3f98f7372..5a5eb5dd1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] @@ -952,6 +952,7 @@ dependencies = [ "log", "miette", "qsc", + "regex", ] [[package]] @@ -1023,9 +1024,21 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.1" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" dependencies = [ "aho-corasick", "memchr", @@ -1034,9 +1047,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" [[package]] name = "rustc-demangle" diff --git a/language_service/Cargo.toml b/language_service/Cargo.toml index 01c18e04a0..3cde319284 100644 --- a/language_service/Cargo.toml +++ b/language_service/Cargo.toml @@ -17,3 +17,4 @@ log = { workspace = true } miette = { workspace = true } qsc = { path = "../compiler/qsc" } enum-iterator = { workspace = true } +regex = "1.9.1" diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index 5a51a7f81d..36b8e3c1f5 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -8,6 +8,7 @@ use crate::display::CodeDisplay; use crate::qsc_utils::{find_item, map_offset, span_contains, Compilation}; use qsc::ast::visit::{walk_callable_decl, walk_expr, walk_pat, walk_ty_def, Visitor}; use qsc::{ast, hir, resolve}; +use regex::Regex; use std::fmt::Display; use std::rc::Rc; @@ -188,18 +189,36 @@ impl Visitor<'_> for HoverVisitor<'_> { } fn markdown_with_doc(doc: &Rc, code: impl Display) -> String { - if doc.is_empty() { + let parsed_doc = parse_doc(doc); + if parsed_doc.is_empty() { markdown_fenced_block(code) } else { format!( "{} {}", - doc, + parsed_doc, markdown_fenced_block(code) ) } } +fn parse_doc(doc: &str) -> &str { + let summary_re = + Regex::new(r"(^|(\r?\n))\s*#\s*((S|s)ummary+)[\s\r\n]*").expect("Invalid regex"); + let header_re = Regex::new(r"\r?\n\s*#\s*(\w+)[\s\n\r]*").expect("Invalid regex"); + let temp = match summary_re.find(doc) { + Some(summary_header) => { + let start = summary_header.end(); + match header_re.find(&doc[start..]) { + Some(next_header) => &doc[start..(start + next_header.start())], + None => &doc[start..], + } + } + None => doc, + }; + temp +} + fn markdown_fenced_block(code: impl Display) -> String { format!( "```qsharp diff --git a/language_service/src/hover/tests.rs b/language_service/src/hover/tests.rs index c4e73f9768..5e410247ec 100644 --- a/language_service/src/hover/tests.rs +++ b/language_service/src/hover/tests.rs @@ -40,12 +40,13 @@ fn hover_callable_unit_types() { check( indoc! {r#" namespace Test { - /// Doc comment! + /// Doc comment + /// with multiple lines! operation ◉B↘ar◉() : Unit {} } "#}, &expect![[r#" - "Doc comment!\n```qsharp\noperation Bar Unit => Unit\n```\n" + "Doc comment\nwith multiple lines!\n```qsharp\noperation Bar Unit => Unit\n```\n" "#]], ); } @@ -516,3 +517,136 @@ fn hover_foreign_call_functors() { "#]], ); } + +#[test] +fn hover_callable_summary() { + check( + indoc! {r#" + namespace Test { + /// # Summary + /// This is a + /// multi-line summary! + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_summary_stuff_before() { + check( + indoc! {r#" + namespace Test { + /// not the summary + /// # Summary + /// This is a + /// multi-line summary! + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_summary_other_header_before() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// This stuff is not the summary. + /// # Summary + /// This is a + /// multi-line summary! + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_summary_other_header_after() { + check( + indoc! {r#" + namespace Test { + /// # Summary + /// This is a + /// multi-line summary! + /// # Not The Summary + /// This stuff is not the summary. + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_summary_other_headers() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// This stuff is not the summary. + /// # Summary + /// This is a + /// multi-line summary! + /// # Also Not The Summary + /// This stuff is also not the summary. + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_headers_but_no_summary() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// This stuff is not the summary. + /// # Also Not The Summary + /// This stuff is also not the summary. + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r##" + "# 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" + "##]], + ); +} + +#[test] +fn hover_callable_summary_only_header_matches() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// This stuff is not the # Summary. + /// # Summary + /// This is a + /// multi-line # Summary! + /// # Also Not The Summary + /// This stuff is also not the # Summary. + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line # Summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} From 72f03f933c6fc8af6a3cd4c586d4e429ff93f38a Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Mon, 10 Jul 2023 15:05:55 -0700 Subject: [PATCH 02/17] remove temp --- language_service/src/hover.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index 36b8e3c1f5..cdd6db1ded 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -206,7 +206,7 @@ fn parse_doc(doc: &str) -> &str { let summary_re = Regex::new(r"(^|(\r?\n))\s*#\s*((S|s)ummary+)[\s\r\n]*").expect("Invalid regex"); let header_re = Regex::new(r"\r?\n\s*#\s*(\w+)[\s\n\r]*").expect("Invalid regex"); - let temp = match summary_re.find(doc) { + match summary_re.find(doc) { Some(summary_header) => { let start = summary_header.end(); match header_re.find(&doc[start..]) { @@ -215,8 +215,7 @@ fn parse_doc(doc: &str) -> &str { } } None => doc, - }; - temp + } } fn markdown_fenced_block(code: impl Display) -> String { From d8ea98f23ca18f15470c95c9887e1883cf8c29a7 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Mon, 10 Jul 2023 15:31:30 -0700 Subject: [PATCH 03/17] Remove LaTex from Summaries --- library/std/arithmetic.qs | 4 +++- library/std/intrinsic.qs | 24 ++++++++++++------------ library/std/math.qs | 22 ++++++++++++++-------- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/library/std/arithmetic.qs b/library/std/arithmetic.qs index 4990af4b92..7f0aacb16a 100644 --- a/library/std/arithmetic.qs +++ b/library/std/arithmetic.qs @@ -122,6 +122,8 @@ namespace Microsoft.Quantum.Arithmetic { /// # Summary /// Reversible, in-place ripple-carry addition of two integers. + /// + /// # Description /// Given two $n$-bit integers encoded in LittleEndian registers `xs` and `ys`, /// and a qubit carry, the operation computes the sum of the two integers /// where the $n$ least significant bits of the result are held in `ys` and @@ -274,7 +276,7 @@ namespace Microsoft.Quantum.Arithmetic { Fact(Length(xs) > 0, "Array should not be empty."); - let nQubits = Length(xs); + let nQubits = Length(xs); for idx in 0..nQubits - 2 { CCNOT(xs[idx], ys[idx], xs[idx+1]); } diff --git a/library/std/intrinsic.qs b/library/std/intrinsic.qs index 89c15c5856..67b369af55 100644 --- a/library/std/intrinsic.qs +++ b/library/std/intrinsic.qs @@ -200,7 +200,7 @@ namespace Microsoft.Quantum.Intrinsic { /// # Summary /// Performs a measurement of a single qubit in the - /// Pauli $Z$ basis. + /// Pauli Z basis. /// /// # Description /// The output result is given by @@ -328,7 +328,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the $\ket{1}$ state by a given angle. + /// Applies a rotation about the `|1>` state by a given angle. /// /// # Description /// \begin{align} @@ -373,7 +373,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the $\ket{1}$ state by an angle specified + /// Applies a rotation about the `|1>` state by an angle specified /// as a dyadic fraction. /// /// # Description @@ -473,7 +473,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the $x$-axis by a given angle. + /// Applies a rotation about the `x`-axis by a given angle. /// /// # Description /// \begin{align} @@ -519,7 +519,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the two qubit Ising $XX$ rotation gate. + /// Applies the two qubit Ising `XX` rotation gate. /// /// # Description /// \begin{align} @@ -567,7 +567,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the $y$-axis by a given angle. + /// Applies a rotation about the `y`-axis by a given angle. /// /// # Description /// \begin{align} @@ -613,7 +613,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the two qubit Ising $YY$ rotation gate. + /// Applies the two qubit Ising `YY` rotation gate. /// /// # Description /// \begin{align} @@ -661,7 +661,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the $z$-axis by a given angle. + /// Applies a rotation about the `z`-axis by a given angle. /// /// # Description /// \begin{align} @@ -712,7 +712,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the two qubit Ising $ZZ$ rotation gate. + /// Applies the two qubit Ising ZZ rotation gate. /// /// # Description /// \begin{align} @@ -943,7 +943,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the Pauli $X$ gate. + /// Applies the Pauli X gate. /// /// # Description /// \begin{align} @@ -990,7 +990,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the Pauli $Y$ gate. + /// Applies the Pauli Y gate. /// /// # Description /// \begin{align} @@ -1037,7 +1037,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the Pauli $Z$ gate. + /// Applies the Pauli Z gate. /// /// # Description /// \begin{align} diff --git a/library/std/math.qs b/library/std/math.qs index c3db744a4d..ee4866feee 100644 --- a/library/std/math.qs +++ b/library/std/math.qs @@ -16,7 +16,7 @@ namespace Microsoft.Quantum.Math { elif (a > 0) { +1 } else { 0 } } - + /// # Summary /// Returns -1, 0 or +1 that indicates the sign of a number. function SignD (a : Double) : Int { @@ -38,7 +38,7 @@ namespace Microsoft.Quantum.Math { function AbsI (a : Int) : Int { a < 0 ? -a | a } - + /// # Summary /// Returns the absolute value of a double-precision floating-point number. function AbsD (a : Double) : Double { @@ -49,7 +49,7 @@ namespace Microsoft.Quantum.Math { function AbsL (a : BigInt) : BigInt { a < 0L ? -a | a } - + /// # Summary /// Returns the larger of two specified numbers. function MaxI(a : Int, b : Int) : Int { @@ -206,7 +206,7 @@ namespace Microsoft.Quantum.Math { } /// # Summary - /// Returns the natural (base $e$) logarithm of a specified number. + /// Returns the natural (base `e`) logarithm of a specified number. function Log(input : Double) : Double { body intrinsic; } @@ -361,8 +361,11 @@ namespace Microsoft.Quantum.Math { } /// # Summary - /// Returns the multiplicative inverse of a modular integer: - /// $b$ such that $a \cdot b = 1 (\operatorname{mod} \texttt{modulus})$. + /// Returns the multiplicative inverse of a modular integer. + /// + /// # Description + /// This will calculate the multiplicative inverse of a + /// modular integer $b$ such that $a \cdot b = 1 (\operatorname{mod} \texttt{modulus})$. function InverseModI(a : Int, modulus : Int) : Int { let (u, v) = ExtendedGreatestCommonDivisorI(a, modulus); let gcd = u * a + v * modulus; @@ -371,8 +374,11 @@ namespace Microsoft.Quantum.Math { } /// # Summary - /// Returns the multiplicative inverse of a modular integer: - /// $b$ such that $a \cdot b = 1 (\operatorname{mod} \texttt{modulus})$. + /// Returns the multiplicative inverse of a modular integer. + /// + /// # Description + /// This will calculate the multiplicative inverse of a + /// modular integer $b$ such that $a \cdot b = 1 (\operatorname{mod} \texttt{modulus})$. function InverseModL(a : BigInt, modulus : BigInt) : BigInt { let (u, v) = ExtendedGreatestCommonDivisorL(a, modulus); let gcd = u * a + v * modulus; From 8d618ff821d80874c12511e72a58f2cac390605f Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Mon, 10 Jul 2023 17:25:40 -0700 Subject: [PATCH 04/17] use italics for math --- library/std/intrinsic.qs | 22 +++++++++++----------- library/std/math.qs | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/library/std/intrinsic.qs b/library/std/intrinsic.qs index 67b369af55..92f698d6b0 100644 --- a/library/std/intrinsic.qs +++ b/library/std/intrinsic.qs @@ -328,7 +328,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the `|1>` state by a given angle. + /// Applies a rotation about the |1⟩ state by a given angle. /// /// # Description /// \begin{align} @@ -373,7 +373,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the `|1>` state by an angle specified + /// Applies a rotation about the |1⟩ state by an angle specified /// as a dyadic fraction. /// /// # Description @@ -473,7 +473,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the `x`-axis by a given angle. + /// Applies a rotation about the _x_-axis by a given angle. /// /// # Description /// \begin{align} @@ -519,7 +519,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the two qubit Ising `XX` rotation gate. + /// Applies the two qubit Ising _XX_ rotation gate. /// /// # Description /// \begin{align} @@ -567,7 +567,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the `y`-axis by a given angle. + /// Applies a rotation about the _y_-axis by a given angle. /// /// # Description /// \begin{align} @@ -613,7 +613,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the two qubit Ising `YY` rotation gate. + /// Applies the two qubit Ising _YY_ rotation gate. /// /// # Description /// \begin{align} @@ -661,7 +661,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the `z`-axis by a given angle. + /// Applies a rotation about the _z_-axis by a given angle. /// /// # Description /// \begin{align} @@ -712,7 +712,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the two qubit Ising ZZ rotation gate. + /// Applies the two qubit Ising _ZZ_ rotation gate. /// /// # Description /// \begin{align} @@ -943,7 +943,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the Pauli X gate. + /// Applies the Pauli _X_ gate. /// /// # Description /// \begin{align} @@ -990,7 +990,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the Pauli Y gate. + /// Applies the Pauli _Y_ gate. /// /// # Description /// \begin{align} @@ -1037,7 +1037,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the Pauli Z gate. + /// Applies the Pauli _Z_ gate. /// /// # Description /// \begin{align} diff --git a/library/std/math.qs b/library/std/math.qs index ee4866feee..fd632687f0 100644 --- a/library/std/math.qs +++ b/library/std/math.qs @@ -206,7 +206,7 @@ namespace Microsoft.Quantum.Math { } /// # Summary - /// Returns the natural (base `e`) logarithm of a specified number. + /// Returns the natural (base _e_) logarithm of a specified number. function Log(input : Double) : Double { body intrinsic; } From 0945482834988574c654dc811f26ced0424509f2 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Mon, 10 Jul 2023 17:52:13 -0700 Subject: [PATCH 05/17] missed one --- library/std/intrinsic.qs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/intrinsic.qs b/library/std/intrinsic.qs index 92f698d6b0..c22697b47d 100644 --- a/library/std/intrinsic.qs +++ b/library/std/intrinsic.qs @@ -200,7 +200,7 @@ namespace Microsoft.Quantum.Intrinsic { /// # Summary /// Performs a measurement of a single qubit in the - /// Pauli Z basis. + /// Pauli _Z_ basis. /// /// # Description /// The output result is given by From 7b3f8ed8c90b8f44562a541b051455797f462e77 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Tue, 11 Jul 2023 11:10:00 -0700 Subject: [PATCH 06/17] parse into struct --- language_service/src/hover.rs | 37 +++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index cdd6db1ded..10d39dfccb 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -24,6 +24,23 @@ pub struct Span { pub end: u32, } +#[allow(dead_code)] +struct ParamDescription { + name: String, + description: String, +} + +#[allow(dead_code)] +struct Documentation { + summary: String, + description: String, + type_parameters: Vec, + input: Vec, + output: String, + remarks: String, + example: String, +} + pub(crate) fn get_hover( compilation: &Compilation, source_name: &str, @@ -190,23 +207,23 @@ impl Visitor<'_> for HoverVisitor<'_> { fn markdown_with_doc(doc: &Rc, code: impl Display) -> String { let parsed_doc = parse_doc(doc); - if parsed_doc.is_empty() { + if parsed_doc.summary.is_empty() { markdown_fenced_block(code) } else { format!( "{} {}", - parsed_doc, + parsed_doc.summary, markdown_fenced_block(code) ) } } -fn parse_doc(doc: &str) -> &str { +fn parse_doc(doc: &str) -> Documentation { let summary_re = Regex::new(r"(^|(\r?\n))\s*#\s*((S|s)ummary+)[\s\r\n]*").expect("Invalid regex"); let header_re = Regex::new(r"\r?\n\s*#\s*(\w+)[\s\n\r]*").expect("Invalid regex"); - match summary_re.find(doc) { + let summary = match summary_re.find(doc) { Some(summary_header) => { let start = summary_header.end(); match header_re.find(&doc[start..]) { @@ -216,6 +233,18 @@ fn parse_doc(doc: &str) -> &str { } None => doc, } + .to_string(); + + // ToDo: Parse the other fields. Currently only summary is parsed. + Documentation { + summary, + description: String::new(), + type_parameters: vec![], + input: vec![], + output: String::new(), + remarks: String::new(), + example: String::new(), + } } fn markdown_fenced_block(code: impl Display) -> String { From 162808c7b61c80b43f0ad4b55598967dac8b16cf Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Tue, 11 Jul 2023 11:48:06 -0700 Subject: [PATCH 07/17] minor change to regex --- language_service/src/hover.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index 10d39dfccb..0ccc38a7eb 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -221,8 +221,8 @@ fn markdown_with_doc(doc: &Rc, code: impl Display) -> String { fn parse_doc(doc: &str) -> Documentation { let summary_re = - Regex::new(r"(^|(\r?\n))\s*#\s*((S|s)ummary+)[\s\r\n]*").expect("Invalid regex"); - let header_re = Regex::new(r"\r?\n\s*#\s*(\w+)[\s\n\r]*").expect("Invalid regex"); + Regex::new(r"(^|(\r?\n))\s*#\s*((S|s)ummary)[\s\r\n]*").expect("Invalid regex"); + let header_re = Regex::new(r"\r?\n\s*#\s*(\w+)[\s\r\n]*").expect("Invalid regex"); let summary = match summary_re.find(doc) { Some(summary_header) => { let start = summary_header.end(); From 0267d82cb4418b4ef5c1d71601db08f7045e4d75 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Tue, 11 Jul 2023 13:04:15 -0700 Subject: [PATCH 08/17] simplified documentation structure --- language_service/src/hover.rs | 24 +----------------------- language_service/src/hover/tests.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index 0ccc38a7eb..052155a608 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -24,21 +24,8 @@ pub struct Span { pub end: u32, } -#[allow(dead_code)] -struct ParamDescription { - name: String, - description: String, -} - -#[allow(dead_code)] struct Documentation { summary: String, - description: String, - type_parameters: Vec, - input: Vec, - output: String, - remarks: String, - example: String, } pub(crate) fn get_hover( @@ -235,16 +222,7 @@ fn parse_doc(doc: &str) -> Documentation { } .to_string(); - // ToDo: Parse the other fields. Currently only summary is parsed. - Documentation { - summary, - description: String::new(), - type_parameters: vec![], - input: vec![], - output: String::new(), - remarks: String::new(), - example: String::new(), - } + Documentation { summary } } fn markdown_fenced_block(code: impl Display) -> String { diff --git a/language_service/src/hover/tests.rs b/language_service/src/hover/tests.rs index 5e410247ec..5f929fdb7e 100644 --- a/language_service/src/hover/tests.rs +++ b/language_service/src/hover/tests.rs @@ -650,3 +650,21 @@ fn hover_callable_summary_only_header_matches() { "#]], ); } + +#[test] +fn hover_callable_summary_successive_headers() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// # Summary + /// This is a + /// multi-line summary! + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} From eba2b239dfa924605029313effab992007d693f8 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Tue, 11 Jul 2023 14:29:53 -0700 Subject: [PATCH 09/17] remove regex --- Cargo.lock | 25 ++++++------------------- language_service/Cargo.toml | 1 - language_service/src/hover.rs | 34 ++++++++++++++++++---------------- 3 files changed, 24 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a5eb5dd1a..b3f98f7372 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -952,7 +952,6 @@ dependencies = [ "log", "miette", "qsc", - "regex", ] [[package]] @@ -1024,21 +1023,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.3.2" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -1047,9 +1034,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "rustc-demangle" diff --git a/language_service/Cargo.toml b/language_service/Cargo.toml index 3cde319284..01c18e04a0 100644 --- a/language_service/Cargo.toml +++ b/language_service/Cargo.toml @@ -17,4 +17,3 @@ log = { workspace = true } miette = { workspace = true } qsc = { path = "../compiler/qsc" } enum-iterator = { workspace = true } -regex = "1.9.1" diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index 052155a608..8e32c49e87 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -8,7 +8,7 @@ use crate::display::CodeDisplay; use crate::qsc_utils::{find_item, map_offset, span_contains, Compilation}; use qsc::ast::visit::{walk_callable_decl, walk_expr, walk_pat, walk_ty_def, Visitor}; use qsc::{ast, hir, resolve}; -use regex::Regex; +//use regex_lite::Regex; use std::fmt::Display; use std::rc::Rc; @@ -207,22 +207,24 @@ fn markdown_with_doc(doc: &Rc, code: impl Display) -> String { } fn parse_doc(doc: &str) -> Documentation { - let summary_re = - Regex::new(r"(^|(\r?\n))\s*#\s*((S|s)ummary)[\s\r\n]*").expect("Invalid regex"); - let header_re = Regex::new(r"\r?\n\s*#\s*(\w+)[\s\r\n]*").expect("Invalid regex"); - let summary = match summary_re.find(doc) { - Some(summary_header) => { - let start = summary_header.end(); - match header_re.find(&doc[start..]) { - Some(next_header) => &doc[start..(start + next_header.start())], - None => &doc[start..], - } - } - None => doc, + // let summary_re = + // Regex::new(r"(^|(\r?\n))\s*#\s*((S|s)ummary)[\s\r\n]*").expect("Invalid regex"); + // let header_re = Regex::new(r"\r?\n\s*#\s*(\w+)[\s\r\n]*").expect("Invalid regex"); + // let summary = match summary_re.find(doc) { + // Some(summary_header) => { + // let start = summary_header.end(); + // match header_re.find(&doc[start..]) { + // Some(next_header) => &doc[start..(start + next_header.start())], + // None => &doc[start..], + // } + // } + // None => doc, + // } + // .to_string(); + + Documentation { + summary: doc.to_string(), } - .to_string(); - - Documentation { summary } } fn markdown_fenced_block(code: impl Display) -> String { From 199870903412b6d53072e6ffdb8dd480f119757a Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Tue, 11 Jul 2023 14:44:23 -0700 Subject: [PATCH 10/17] use regex_lite --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + language_service/Cargo.toml | 1 + language_service/src/hover.rs | 34 ++++++++++++++++------------------ 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3f98f7372..c3076a57de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -952,6 +952,7 @@ dependencies = [ "log", "miette", "qsc", + "regex-lite", ] [[package]] @@ -1032,6 +1033,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f96ede7f386ba6e910092e7ccdc04176cface62abebea07ed6b46d870ed95ca2" + [[package]] name = "regex-syntax" version = "0.6.28" diff --git a/Cargo.toml b/Cargo.toml index 2ccf92ebe8..9edc33827f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ num-bigint = "0.4.3" num-complex = "0.4" num-traits = "0.2.15" indenter = "0.2" +regex-lite = "0.1.0" serde = "1.0" serde-wasm-bindgen = "0.5" wasm-bindgen = "0.2.84" diff --git a/language_service/Cargo.toml b/language_service/Cargo.toml index 01c18e04a0..a85e8d5a0c 100644 --- a/language_service/Cargo.toml +++ b/language_service/Cargo.toml @@ -17,3 +17,4 @@ log = { workspace = true } miette = { workspace = true } qsc = { path = "../compiler/qsc" } enum-iterator = { workspace = true } +regex-lite = { workspace = true } diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index 8e32c49e87..b185afff91 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -8,7 +8,7 @@ use crate::display::CodeDisplay; use crate::qsc_utils::{find_item, map_offset, span_contains, Compilation}; use qsc::ast::visit::{walk_callable_decl, walk_expr, walk_pat, walk_ty_def, Visitor}; use qsc::{ast, hir, resolve}; -//use regex_lite::Regex; +use regex_lite::Regex; use std::fmt::Display; use std::rc::Rc; @@ -207,24 +207,22 @@ fn markdown_with_doc(doc: &Rc, code: impl Display) -> String { } fn parse_doc(doc: &str) -> Documentation { - // let summary_re = - // Regex::new(r"(^|(\r?\n))\s*#\s*((S|s)ummary)[\s\r\n]*").expect("Invalid regex"); - // let header_re = Regex::new(r"\r?\n\s*#\s*(\w+)[\s\r\n]*").expect("Invalid regex"); - // let summary = match summary_re.find(doc) { - // Some(summary_header) => { - // let start = summary_header.end(); - // match header_re.find(&doc[start..]) { - // Some(next_header) => &doc[start..(start + next_header.start())], - // None => &doc[start..], - // } - // } - // None => doc, - // } - // .to_string(); - - Documentation { - summary: doc.to_string(), + let summary_re = + Regex::new(r"(^|(\r?\n))\s*#\s*((S|s)ummary)[\s\r\n]*").expect("Invalid regex"); + let header_re = Regex::new(r"\r?\n\s*#\s*(\w+)[\s\r\n]*").expect("Invalid regex"); + let summary = match summary_re.find(doc) { + Some(summary_header) => { + let start = summary_header.end(); + match header_re.find(&doc[start..]) { + Some(next_header) => &doc[start..(start + next_header.start())], + None => &doc[start..], + } + } + None => doc, } + .to_string(); + + Documentation { summary } } fn markdown_fenced_block(code: impl Display) -> String { From 994177507cdd6051af6015fd80c6b0a3531d81b1 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Tue, 11 Jul 2023 17:20:26 -0700 Subject: [PATCH 11/17] use capture regex --- language_service/src/hover.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index b185afff91..dde27d83a6 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -207,16 +207,14 @@ fn markdown_with_doc(doc: &Rc, code: impl Display) -> String { } fn parse_doc(doc: &str) -> Documentation { - let summary_re = - Regex::new(r"(^|(\r?\n))\s*#\s*((S|s)ummary)[\s\r\n]*").expect("Invalid regex"); - let header_re = Regex::new(r"\r?\n\s*#\s*(\w+)[\s\r\n]*").expect("Invalid regex"); - let summary = match summary_re.find(doc) { - Some(summary_header) => { - let start = summary_header.end(); - match header_re.find(&doc[start..]) { - Some(next_header) => &doc[start..(start + next_header.start())], - None => &doc[start..], - } + let re = Regex::new(r"(?m)(?:^#\s*Summary\s*$\s*)([\s\S]+?)(?:\s*(^#.*)|\z)") + .expect("Invalid regex"); + let summary = match re.captures(doc) { + Some(captures) => { + let capture = captures + .get(1) + .expect("Didn't find the capture for the given regex"); + &doc[capture.start()..capture.end()] } None => doc, } From add1a3919210de56202c339f20a1b8f1f5f5ae1c Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Tue, 11 Jul 2023 17:23:35 -0700 Subject: [PATCH 12/17] empty summary test --- language_service/src/hover.rs | 2 +- language_service/src/hover/tests.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index dde27d83a6..de912fe1f1 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -207,7 +207,7 @@ fn markdown_with_doc(doc: &Rc, code: impl Display) -> String { } fn parse_doc(doc: &str) -> Documentation { - let re = Regex::new(r"(?m)(?:^#\s*Summary\s*$\s*)([\s\S]+?)(?:\s*(^#.*)|\z)") + let re = Regex::new(r"(?m)(?:^#\s*Summary\s*$\s*)([\s\S]*?)(?:\s*(^#.*)|\z)") .expect("Invalid regex"); let summary = match re.captures(doc) { Some(captures) => { diff --git a/language_service/src/hover/tests.rs b/language_service/src/hover/tests.rs index 5f929fdb7e..f2bcc042ed 100644 --- a/language_service/src/hover/tests.rs +++ b/language_service/src/hover/tests.rs @@ -668,3 +668,20 @@ fn hover_callable_summary_successive_headers() { "#]], ); } + +#[test] +fn hover_callable_empty_summary() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// # Summary + /// # Also Not The Summary + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r##" + "```qsharp\noperation Foo Unit => Unit\n```\n" + "##]], + ); +} From 2d0edba38c73d39d70e61c93f7f0a1dd90900ae3 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Tue, 11 Jul 2023 17:28:21 -0700 Subject: [PATCH 13/17] minor changes to regex --- language_service/src/hover.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index de912fe1f1..d765eac4af 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -207,7 +207,7 @@ fn markdown_with_doc(doc: &Rc, code: impl Display) -> String { } fn parse_doc(doc: &str) -> Documentation { - let re = Regex::new(r"(?m)(?:^#\s*Summary\s*$\s*)([\s\S]*?)(?:\s*(^#.*)|\z)") + let re = Regex::new(r"(?mi)(?:^#\s+Summary\s*$\s*)([\s\S]*?)(?:\s*(^# .*)|\z)") .expect("Invalid regex"); let summary = match re.captures(doc) { Some(captures) => { From 18887d5dafb5e32e79fb11b36d4b1a526cb007b3 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Tue, 11 Jul 2023 17:32:12 -0700 Subject: [PATCH 14/17] use capture directly --- language_service/src/hover.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index d765eac4af..c2d99d7222 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -214,7 +214,7 @@ fn parse_doc(doc: &str) -> Documentation { let capture = captures .get(1) .expect("Didn't find the capture for the given regex"); - &doc[capture.start()..capture.end()] + capture.as_str() } None => doc, } From 4523f10d64aee323a4b7543d52b898c6180d8191 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Wed, 12 Jul 2023 10:05:35 -0700 Subject: [PATCH 15/17] simplified `find_item` function --- language_service/src/qsc_utils.rs | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/language_service/src/qsc_utils.rs b/language_service/src/qsc_utils.rs index 3a8ba4b9be..57adcf0a0e 100644 --- a/language_service/src/qsc_utils.rs +++ b/language_service/src/qsc_utils.rs @@ -46,32 +46,15 @@ pub(crate) fn map_offset(source_map: &SourceMap, source_name: &str, source_offse } pub(crate) fn find_item<'a>(compilation: &'a Compilation, id: &ItemId) -> Option<&'a Item> { - let mut finder_pass = FindItem { - id: &id.item, - item: None, - }; let package = if let Some(package_id) = id.package { - &compilation - .package_store - .get(package_id) - .unwrap_or_else(|| panic!("bad package id: {package_id}")) - .package + match &compilation.package_store.get(package_id) { + Some(compilation) => &compilation.package, + None => { + return None; + } + } } else { &compilation.unit.package }; - finder_pass.visit_package(package); - finder_pass.item -} - -struct FindItem<'a, 'b> { - pub id: &'a LocalItemId, - pub item: Option<&'b Item>, -} - -impl<'a, 'b> Visitor<'b> for FindItem<'a, 'b> { - fn visit_item(&mut self, item: &'b Item) { - if item.id == *self.id { - self.item = Some(item); - } - } + package.items.get(id.item) } From 3ce2f9a3025a324a2fcc7af01edf4ff95b5f43d4 Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Wed, 12 Jul 2023 10:06:59 -0700 Subject: [PATCH 16/17] forgot something --- language_service/src/qsc_utils.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/language_service/src/qsc_utils.rs b/language_service/src/qsc_utils.rs index 57adcf0a0e..d31a9d8b3b 100644 --- a/language_service/src/qsc_utils.rs +++ b/language_service/src/qsc_utils.rs @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use qsc::hir::visit::Visitor; -use qsc::hir::{Item, ItemId, LocalItemId, PackageId}; +use qsc::hir::{Item, ItemId, PackageId}; use qsc::{ compile::{self, Error}, PackageStore, SourceMap, From 534698b820d9359e6a664504ddb9104ee0173cac Mon Sep 17 00:00:00 2001 From: Scott Carda Date: Thu, 13 Jul 2023 10:21:50 -0700 Subject: [PATCH 17/17] use trim instead --- language_service/src/hover.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index c2d99d7222..9c03e0fa8a 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -207,8 +207,7 @@ fn markdown_with_doc(doc: &Rc, code: impl Display) -> String { } fn parse_doc(doc: &str) -> Documentation { - let re = Regex::new(r"(?mi)(?:^#\s+Summary\s*$\s*)([\s\S]*?)(?:\s*(^# .*)|\z)") - .expect("Invalid regex"); + let re = Regex::new(r"(?mi)(?:^# Summary$)([\s\S]*?)(?:(^# .*)|\z)").expect("Invalid regex"); let summary = match re.captures(doc) { Some(captures) => { let capture = captures @@ -218,6 +217,7 @@ fn parse_doc(doc: &str) -> Documentation { } None => doc, } + .trim() .to_string(); Documentation { summary }