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/completion.rs

280lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use crate::qsc_utils::{map_offset, span_contains, Compilation};
8use qsc::ast::visit::{self, Visitor};
9use qsc::hir::{ItemKind, Package, PackageId};
10
11#[derive(Copy, Clone, Debug, PartialEq)]
12#[allow(clippy::module_name_repetitions)]
13pub enum CompletionItemKind {
14 // It would have been nice to match these enum values to the ones used by
15 // VS Code and Monaco, but unfortunately those two disagree on the values.
16 // So we define our own unique enum here to reduce confusion.
17 Function,
18 Interface,
19 Keyword,
20 Module,
21}
22
23#[derive(Debug)]
24#[allow(clippy::module_name_repetitions)]
25pub struct CompletionList {
26 pub items: Vec<CompletionItem>,
27}
28
29#[derive(Clone, Debug, PartialEq)]
30#[allow(clippy::module_name_repetitions)]
31pub struct CompletionItem {
32 pub label: String,
33 pub kind: CompletionItemKind,
34 pub sort_text: Option<String>,
35}
36
37pub(crate) fn get_completions(
38 compilation: &Compilation,
39 source_name: &str,
40 offset: u32,
41) -> CompletionList {
42 // Map the file offset into a SourceMap offset
43 let offset = map_offset(&compilation.unit.sources, source_name, offset);
44
45 // Determine context for the offset
46 let mut context_finder = ContextFinder {
47 offset,
48 context: if compilation.unit.ast.package.namespaces.as_ref().is_empty() {
49 // The parser failed entirely, no context to go on
50 Context::NoCompilation
51 } else {
52 // Starting context is top-level (i.e. outside a namespace block)
53 Context::TopLevel
54 },
55 };
56 context_finder.visit_package(&compilation.unit.ast.package);
57
58 // We don't attempt to be comprehensive or accurate when determining completions,
59 // since that's not really possible without more sophisticated error recovery
60 // in the parser or the ability for the resolver to gather all
61 // appropriate names for a scope. These are not done at the moment.
62
63 // So the following is an attempt to get "good enough" completions, tuned
64 // based on the user experience of typing out a few samples in the editor.
65
66 let mut builder = CompletionListBuilder::new();
67 match context_finder.context {
68 Context::Namespace => {
69 // Include "open", "operation", etc
70 builder.push_item_decl_keywords();
71
72 // Typing into a callable decl sometimes breaks the
73 // parser and the context appears to be a namespace block,
74 // so just include everything that may be relevant
75 builder.push_stmt_keywords();
76 builder.push_expr_keywords();
77 builder.push_types();
78 builder.push_globals(compilation);
79 }
80
81 Context::CallableSignature => {
82 builder.push_types();
83 }
84 Context::Block => {
85 // Pretty much anything goes in a block
86 builder.push_stmt_keywords();
87 builder.push_expr_keywords();
88 builder.push_types();
89 builder.push_globals(compilation);
90
91 // Item decl keywords last, unlike in a namespace
92 builder.push_item_decl_keywords();
93 }
94 Context::NoCompilation | Context::TopLevel => {
95 builder.push_namespace_keyword();
96 }
97 }
98
99 CompletionList {
100 items: builder.into_items(),
101 }
102}
103
104struct CompletionListBuilder {
105 current_sort_group: u32,
106 items: Vec<CompletionItem>,
107}
108
109impl CompletionListBuilder {
110 fn new() -> Self {
111 CompletionListBuilder {
112 current_sort_group: 1,
113 items: Vec::new(),
114 }
115 }
116
117 fn into_items(self) -> Vec<CompletionItem> {
118 self.items
119 }
120
121 fn push_item_decl_keywords(&mut self) {
122 static ITEM_KEYWORDS: [&str; 5] = ["operation", "open", "internal", "function", "newtype"];
123
124 self.push_completions(ITEM_KEYWORDS.into_iter(), CompletionItemKind::Keyword);
125 }
126
127 fn push_namespace_keyword(&mut self) {
128 self.push_completions(["namespace"].into_iter(), CompletionItemKind::Keyword);
129 }
130
131 fn push_types(&mut self) {
132 static PRIMITIVE_TYPES: [&str; 10] = [
133 "Qubit", "Int", "Unit", "Result", "Bool", "BigInt", "Double", "Pauli", "Range",
134 "String",
135 ];
136 static FUNCTOR_KEYWORDS: [&str; 3] = ["Adj", "Ctl", "is"];
137
138 self.push_completions(PRIMITIVE_TYPES.into_iter(), CompletionItemKind::Interface);
139 self.push_completions(FUNCTOR_KEYWORDS.into_iter(), CompletionItemKind::Keyword);
140 }
141
142 fn push_globals(&mut self, compilation: &Compilation) {
143 let current = &compilation.unit.package;
144 let std = &compilation
145 .package_store
146 .get(compilation.std_package_id)
147 .expect("expected to find std package")
148 .package;
149 let core = &compilation
150 .package_store
151 .get(PackageId::CORE)
152 .expect("expected to find core package")
153 .package;
154
155 self.push_sorted_completions(Self::get_callables(current), CompletionItemKind::Function);
156 self.push_sorted_completions(Self::get_callables(std), CompletionItemKind::Function);
157 self.push_sorted_completions(Self::get_callables(core), CompletionItemKind::Function);
158 self.push_completions(Self::get_namespaces(current), CompletionItemKind::Module);
159 self.push_completions(Self::get_namespaces(std), CompletionItemKind::Module);
160 self.push_completions(Self::get_namespaces(core), CompletionItemKind::Module);
161 }
162
163 fn push_stmt_keywords(&mut self) {
164 static STMT_KEYWORDS: [&str; 5] = ["let", "return", "use", "mutable", "borrow"];
165
166 self.push_completions(STMT_KEYWORDS.into_iter(), CompletionItemKind::Keyword);
167 }
168
169 fn push_expr_keywords(&mut self) {
170 static EXPR_KEYWORDS: [&str; 11] = [
171 "if", "for", "in", "within", "apply", "repeat", "until", "fixup", "set", "while",
172 "fail",
173 ];
174
175 self.push_completions(EXPR_KEYWORDS.into_iter(), CompletionItemKind::Keyword);
176 }
177
178 /// Each invocation of this function increments the sort group so that
179 /// in the eventual completion list, the groups of items show up in the
180 /// order they were added.
181 /// The items are then sorted according to the input list order (not alphabetical)
182 fn push_completions<'a, I>(&mut self, iter: I, kind: CompletionItemKind)
183 where
184 I: Iterator<Item = &'a str>,
185 {
186 let mut current_sort_prefix = 0;
187
188 self.items.extend(iter.map(|name| CompletionItem {
189 label: name.to_string(),
190 kind,
191 sort_text: {
192 current_sort_prefix += 1;
193 Some(format!(
194 "{:02}{:02}{}",
195 self.current_sort_group, current_sort_prefix, name
196 ))
197 },
198 }));
199
200 self.current_sort_group += 1;
201 }
202
203 /// Push a group of completions that are themselves sorted into subgroups
204 fn push_sorted_completions<'a, I>(&mut self, iter: I, kind: CompletionItemKind)
205 where
206 I: Iterator<Item = (&'a str, u32)>,
207 {
208 self.items
209 .extend(iter.map(|(name, item_sort_group)| CompletionItem {
210 label: name.to_string(),
211 kind,
212 sort_text: Some(format!(
213 "{:02}{:02}{}",
214 self.current_sort_group, item_sort_group, name
215 )),
216 }));
217
218 self.current_sort_group += 1;
219 }
220
221 fn get_callables(package: &Package) -> impl Iterator<Item = (&str, u32)> {
222 package.items.values().filter_map(|i| match &i.kind {
223 ItemKind::Callable(callable_decl) => Some({
224 let name = callable_decl.name.name.as_ref();
225 // Everything that starts with a __ goes last in the list
226 let sort_group = u32::from(name.starts_with("__"));
227 (name, sort_group)
228 }),
229 _ => None,
230 })
231 }
232
233 fn get_namespaces(package: &Package) -> impl Iterator<Item = &str> {
234 package.items.values().filter_map(|i| match &i.kind {
235 ItemKind::Namespace(namespace, _) => Some(namespace.name.as_ref()),
236 _ => None,
237 })
238 }
239}
240
241struct ContextFinder {
242 offset: u32,
243 context: Context,
244}
245
246#[derive(Debug, PartialEq)]
247enum Context {
248 NoCompilation,
249 TopLevel,
250 Namespace,
251 CallableSignature,
252 Block,
253}
254
255impl Visitor<'_> for ContextFinder {
256 fn visit_namespace(&mut self, namespace: &'_ qsc::ast::Namespace) {
257 if span_contains(namespace.span, self.offset) {
258 self.context = Context::Namespace;
259 }
260
261 visit::walk_namespace(self, namespace);
262 }
263
264 fn visit_callable_decl(&mut self, decl: &'_ qsc::ast::CallableDecl) {
265 if span_contains(decl.span, self.offset) {
266 // This span covers the body too, but the
267 // context will get overwritten by visit_block
268 // if the offset is inside the actual body
269 self.context = Context::CallableSignature;
270 }
271
272 visit::walk_callable_decl(self, decl);
273 }
274
275 fn visit_block(&mut self, block: &'_ qsc::ast::Block) {
276 if span_contains(block.span, self.offset) {
277 self.context = Context::Block;
278 }
279 }
280}
281