microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
3fc684b157698c92d12be32ea3c6f7b3521de2b3

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

language_service/src/hover.rs

233lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use crate::display::CodeDisplay;
8use crate::qsc_utils::{find_item, map_offset, span_contains, Compilation};
9use qsc::ast::visit::{walk_callable_decl, walk_expr, walk_pat, walk_ty_def, Visitor};
10use qsc::{ast, hir, resolve};
11use regex_lite::Regex;
12use std::fmt::Display;
13use std::rc::Rc;
14
15#[derive(Debug, PartialEq)]
16pub struct Hover {
17 pub contents: String,
18 pub span: Span,
19}
20
21#[derive(Debug, PartialEq)]
22pub struct Span {
23 pub start: u32,
24 pub end: u32,
25}
26
27struct Documentation {
28 summary: String,
29}
30
31pub(crate) fn get_hover(
32 compilation: &Compilation,
33 source_name: &str,
34 offset: u32,
35) -> Option<Hover> {
36 // Map the file offset into a SourceMap offset
37 let offset = map_offset(&compilation.unit.sources, source_name, offset);
38 let package = &compilation.unit.ast.package;
39
40 let mut hover_visitor = HoverVisitor {
41 compilation,
42 offset,
43 contents: None,
44 start: 0,
45 end: 0,
46 display: CodeDisplay { compilation },
47 };
48
49 hover_visitor.visit_package(package);
50
51 hover_visitor.contents.map(|contents| Hover {
52 contents,
53 span: Span {
54 start: hover_visitor.start,
55 end: hover_visitor.end,
56 },
57 })
58}
59
60struct HoverVisitor<'a> {
61 compilation: &'a Compilation,
62 offset: u32,
63 contents: Option<String>,
64 start: u32,
65 end: u32,
66 display: CodeDisplay<'a>,
67}
68
69impl Visitor<'_> for HoverVisitor<'_> {
70 fn visit_item(&mut self, item: &'_ ast::Item) {
71 if span_contains(item.span, self.offset) {
72 match &*item.kind {
73 ast::ItemKind::Callable(decl) => {
74 if span_contains(decl.name.span, self.offset) {
75 self.contents = Some(markdown_with_doc(
76 &item.doc,
77 self.display.ast_callable_decl(decl),
78 ));
79 self.start = decl.name.span.lo;
80 self.end = decl.name.span.hi;
81 } else if span_contains(decl.span, self.offset) {
82 walk_callable_decl(self, decl);
83 }
84 }
85 ast::ItemKind::Ty(ident, def) => {
86 if span_contains(ident.span, self.offset) {
87 self.contents =
88 Some(markdown_fenced_block(self.display.ident_ty_def(ident, def)));
89 self.start = ident.span.lo;
90 self.end = ident.span.hi;
91 } else {
92 self.visit_ty_def(def);
93 }
94 }
95 _ => {}
96 }
97 }
98 }
99
100 fn visit_ty_def(&mut self, def: &'_ ast::TyDef) {
101 if span_contains(def.span, self.offset) {
102 if let ast::TyDefKind::Field(ident, ty) = &*def.kind {
103 if let Some(ident) = ident {
104 if span_contains(ident.span, self.offset) {
105 self.contents =
106 Some(markdown_fenced_block(self.display.ident_ty(ident, ty)));
107 self.start = ident.span.lo;
108 self.end = ident.span.hi;
109 } else {
110 self.visit_ty(ty);
111 }
112 } else {
113 self.visit_ty(ty);
114 }
115 } else {
116 walk_ty_def(self, def);
117 }
118 }
119 }
120
121 fn visit_pat(&mut self, pat: &'_ ast::Pat) {
122 if span_contains(pat.span, self.offset) {
123 match &*pat.kind {
124 ast::PatKind::Bind(ident, anno) => {
125 if span_contains(ident.span, self.offset) {
126 self.contents = Some(markdown_fenced_block(
127 self.display.ident_ty_id(ident, pat.id),
128 ));
129 self.start = ident.span.lo;
130 self.end = ident.span.hi;
131 } else if let Some(ty) = anno {
132 self.visit_ty(ty);
133 }
134 }
135 _ => walk_pat(self, pat),
136 }
137 }
138 }
139
140 fn visit_expr(&mut self, expr: &'_ ast::Expr) {
141 if span_contains(expr.span, self.offset) {
142 match &*expr.kind {
143 ast::ExprKind::Field(_, field) if span_contains(field.span, self.offset) => {
144 self.contents = Some(markdown_fenced_block(
145 self.display.ident_ty_id(field, expr.id),
146 ));
147 self.start = field.span.lo;
148 self.end = field.span.hi;
149 }
150 _ => walk_expr(self, expr),
151 }
152 }
153 }
154
155 fn visit_path(&mut self, path: &'_ ast::Path) {
156 if span_contains(path.span, self.offset) {
157 let res = self.compilation.unit.ast.names.get(path.id);
158 if let Some(res) = res {
159 match &res {
160 resolve::Res::Item(item_id) => {
161 if let Some(item) = find_item(self.compilation, item_id) {
162 self.contents = match &item.kind {
163 hir::ItemKind::Callable(decl) => Some(markdown_with_doc(
164 &item.doc,
165 self.display.hir_callable_decl(decl),
166 )),
167 hir::ItemKind::Namespace(_, _) => {
168 panic!(
169 "Reference node should not refer to a namespace: {}",
170 path.id
171 )
172 }
173 hir::ItemKind::Ty(ident, udt) => Some(markdown_fenced_block(
174 self.display.hir_ident_udt(ident, udt),
175 )),
176 };
177 self.start = path.span.lo;
178 self.end = path.span.hi;
179 }
180 }
181 resolve::Res::Local(node_id) => {
182 self.contents = Some(markdown_fenced_block(
183 self.display.path_ty_id(path, *node_id),
184 ));
185 self.start = path.span.lo;
186 self.end = path.span.hi;
187 }
188 _ => {}
189 };
190 }
191 }
192 }
193}
194
195fn markdown_with_doc(doc: &Rc<str>, code: impl Display) -> String {
196 let parsed_doc = parse_doc(doc);
197 if parsed_doc.summary.is_empty() {
198 markdown_fenced_block(code)
199 } else {
200 format!(
201 "{}
202{}",
203 parsed_doc.summary,
204 markdown_fenced_block(code)
205 )
206 }
207}
208
209fn 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
226fn markdown_fenced_block(code: impl Display) -> String {
227 format!(
228 "```qsharp
229{code}
230```
231"
232 )
233}
234