microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1d2f76e63ca3b68b75bc0cbd77d067a3d5f03901

Branches

Tags

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

Clone

HTTPS

Download ZIP

language_service/src/lib.rs

250lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![warn(clippy::mod_module_files, clippy::pedantic, clippy::unwrap_used)]
5
6use crate::qsc_utils::compile_document;
7use log::trace;
8use protocol::{CompletionList, Definition, Hover, SignatureHelp, WorkspaceConfigurationUpdate};
9use qsc::{PackageType, TargetProfile};
10use qsc_utils::Compilation;
11use std::collections::HashMap;
12
13pub mod completion;
14pub mod definition;
15mod display;
16pub mod hover;
17pub mod protocol;
18mod qsc_utils;
19pub mod signature_help;
20#[cfg(test)]
21mod test_utils;
22#[cfg(test)]
23mod tests;
24
25pub struct LanguageService<'a> {
26 /// Workspace configuration can include compiler settings
27 /// that affect error checking and other language server behavior.
28 /// Currently these settings apply to all documents in the
29 /// workspace. Per-document configurations are not supported.
30 configuration: WorkspaceConfiguration,
31 /// Associate each known document with a separate compilation.
32 document_map: HashMap<String, DocumentState>,
33 /// Callback which will receive diagnostics (compilation errors)
34 /// whenever a (re-)compilation occurs.
35 diagnostics_receiver: Box<DiagnosticsReceiver<'a>>,
36}
37
38#[derive(Debug)]
39struct WorkspaceConfiguration {
40 pub target_profile: TargetProfile,
41 pub package_type: PackageType,
42}
43
44impl Default for WorkspaceConfiguration {
45 fn default() -> Self {
46 Self {
47 target_profile: TargetProfile::Full,
48 package_type: PackageType::Exe,
49 }
50 }
51}
52
53struct DocumentState {
54 /// This version is the document version provided by the client.
55 /// It increases strictly with each text change, though this knowledge should
56 /// not be important. The version is only ever used when publishing
57 /// diagnostics to help the client associate the list of diagnostics
58 /// with a snapshot of the document.
59 pub version: u32,
60 pub compilation: Compilation,
61}
62
63type DiagnosticsReceiver<'a> = dyn Fn(&str, u32, &[qsc::compile::Error]) + 'a;
64
65impl<'a> LanguageService<'a> {
66 pub fn new(diagnostics_receiver: impl Fn(&str, u32, &[qsc::compile::Error]) + 'a) -> Self {
67 LanguageService {
68 configuration: WorkspaceConfiguration::default(),
69 document_map: HashMap::new(),
70 diagnostics_receiver: Box::new(diagnostics_receiver),
71 }
72 }
73
74 /// Updates the workspace configuration. If any compiler settings are updated,
75 /// a recompilation may be triggered, which will results in a new set of diagnostics
76 /// being published.
77 pub fn update_configuration(&mut self, configuration: &WorkspaceConfigurationUpdate) {
78 trace!("update_configuration: {configuration:?}");
79
80 let need_recompile = self.apply_configuration(configuration);
81
82 // Some configuration options require a recompilation as they impact error checking
83 if need_recompile {
84 self.recompile_all();
85 }
86 }
87
88 /// Indicates that the document has been opened or the source has been updated.
89 /// This should be called before any language service requests have been made
90 /// for the document, typically when the document is first opened in the editor.
91 /// It should also be called whenever the source code is updated.
92 pub fn update_document(&mut self, uri: &str, version: u32, text: &str) {
93 trace!("update_document: {uri:?} {version:?}");
94 let compilation = compile_document(
95 uri,
96 text,
97 self.configuration.package_type,
98 self.configuration.target_profile,
99 );
100 let errors = compilation.errors.clone();
101
102 // insert() will update the value if the key already exists
103 self.document_map.insert(
104 uri.to_string(),
105 DocumentState {
106 version,
107 compilation,
108 },
109 );
110
111 trace!("publishing diagnostics for {uri:?}: {errors:?}");
112
113 // Publish diagnostics
114 (self.diagnostics_receiver)(uri, version, &errors);
115 }
116
117 /// Indicates that the client is no longer interested in the document,
118 /// typically occurs when the document is closed in the editor.
119 /// # Panics
120 ///
121 /// This function will panic if compiler state is invalid or in out-of-memory conditions.
122 pub fn close_document(&mut self, uri: &str) {
123 trace!("close_document: {uri:?}");
124 let document_state = self.document_map.remove(uri);
125
126 // Clear the diagnostics, as each document represents
127 // a separate compilation that disappears when the document is closed.
128 (self.diagnostics_receiver)(
129 uri,
130 document_state
131 .expect("close_document received for unknown uri")
132 .version,
133 &[],
134 );
135 }
136
137 /// # Panics
138 ///
139 /// This function will panic if compiler state is invalid or in out-of-memory conditions.
140 #[must_use]
141 pub fn get_completions(&self, uri: &str, offset: u32) -> CompletionList {
142 trace!("get_completions: uri: {uri:?}, offset: {offset:?}");
143 let res = completion::get_completions(
144 &self
145 .document_map.get(uri).as_ref()
146 .expect("get_completions should not be called before document has been initialized with update_document").compilation,
147 uri,
148 offset,
149 );
150 trace!("get_completions result: {res:?}");
151 res
152 }
153
154 /// # Panics
155 ///
156 /// This function will panic if compiler state is invalid or in out-of-memory conditions.
157 #[must_use]
158 pub fn get_definition(&self, uri: &str, offset: u32) -> Option<Definition> {
159 trace!("get_definition: uri: {uri:?}, offset: {offset:?}");
160 let res = definition::get_definition(
161 &self
162 .document_map.get(uri).as_ref()
163 .expect("get_definition should not be called before document has been initialized with update_document").compilation,
164 uri, offset);
165 trace!("get_definition result: {res:?}");
166 res
167 }
168
169 /// # Panics
170 ///
171 /// This function will panic if compiler state is invalid or in out-of-memory conditions.
172 #[must_use]
173 pub fn get_hover(&self, uri: &str, offset: u32) -> Option<Hover> {
174 trace!("get_hover: uri: {uri:?}, offset: {offset:?}");
175 let res = hover::get_hover(
176 &self
177 .document_map.get(uri).as_ref()
178 .expect("get_hover should not be called before document has been initialized with update_document").compilation,
179 uri, offset);
180 trace!("get_hover result: {res:?}");
181 res
182 }
183
184 /// # Panics
185 ///
186 /// This function will panic if compiler state is invalid or in out-of-memory conditions.
187 #[must_use]
188 pub fn get_signature_help(&self, uri: &str, offset: u32) -> Option<SignatureHelp> {
189 trace!("get_signature_help: uri: {uri:?}, offset: {offset:?}");
190 let res = signature_help::get_signature_help(
191 &self
192 .document_map.get(uri).as_ref()
193 .expect("get_signature_help should not be called before document has been initialized with update_document").compilation,
194 uri, offset);
195 trace!("get_signature_help result: {res:?}");
196 res
197 }
198
199 fn apply_configuration(&mut self, configuration: &WorkspaceConfigurationUpdate) -> bool {
200 let mut need_recompile = false;
201
202 if let Some(package_type) = configuration.package_type {
203 need_recompile |= self.configuration.package_type != package_type;
204 self.configuration.package_type = package_type;
205 }
206
207 if let Some(target_profile) = configuration.target_profile {
208 need_recompile |= self.configuration.target_profile != target_profile;
209 self.configuration.target_profile = target_profile;
210 }
211
212 trace!("need_recompile after configuration update: {need_recompile:?}");
213 need_recompile
214 }
215
216 /// Recompiles the currently known documents with
217 /// the current configuration. Publishes updated
218 /// diagnostics for all documents.
219 fn recompile_all(&mut self) {
220 for (uri, state) in &mut self.document_map {
221 let version = state.version;
222 let contents = &state
223 .compilation
224 .unit
225 .sources
226 .find_by_name(uri)
227 .expect("source should be found")
228 .contents;
229
230 let compilation = compile_document(
231 uri,
232 contents,
233 self.configuration.package_type,
234 self.configuration.target_profile,
235 );
236
237 *state = DocumentState {
238 version,
239 compilation,
240 };
241
242 trace!(
243 "publishing diagnostics for {uri:?}: {:?}",
244 state.compilation.errors
245 );
246
247 (self.diagnostics_receiver)(uri, version, &state.compilation.errors);
248 }
249 }
250}
251