microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.23.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/npm/qsharp/src/language-service/language-service.ts

348lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4import type {
5 ICodeAction,
6 ICodeLens,
7 ICompletionList,
8 IHover,
9 ILocation,
10 INotebookMetadata,
11 IPosition,
12 IRange,
13 ISignatureHelp,
14 ITextEdit,
15 IWorkspaceConfiguration,
16 IWorkspaceEdit,
17 LanguageService,
18 VSDiagnostic,
19 ITestDescriptor,
20} from "../../lib/web/qsc_wasm.js";
21import { IProjectHost } from "../browser.js";
22import { log } from "../log.js";
23import {
24 IServiceEventTarget,
25 IServiceProxy,
26 ServiceProtocol,
27} from "../workers/common.js";
28type QscWasm = typeof import("../../lib/web/qsc_wasm.js");
29
30export type LanguageServiceDiagnosticEvent = {
31 type: "diagnostics";
32 detail: {
33 uri: string;
34 version: number;
35 diagnostics: VSDiagnostic[];
36 };
37};
38
39export type LanguageServiceTestCallablesEvent = {
40 type: "testCallables";
41 detail: {
42 callables: ITestDescriptor[];
43 };
44};
45
46export type LanguageServiceEvent =
47 | LanguageServiceDiagnosticEvent
48 | LanguageServiceTestCallablesEvent;
49
50// These need to be async/promise results for when communicating across a WebWorker, however
51// for running the compiler in the same thread the result will be synchronous (a resolved promise).
52export interface ILanguageService {
53 updateConfiguration(config: IWorkspaceConfiguration): Promise<void>;
54 updateDocument(
55 uri: string,
56 version: number,
57 code: string,
58 languageId?: string,
59 ): Promise<void>;
60 updateNotebookDocument(
61 notebookUri: string,
62 version: number,
63 metadata: INotebookMetadata,
64 cells: {
65 uri: string;
66 version: number;
67 code: string;
68 }[],
69 ): Promise<void>;
70 closeDocument(uri: string, languageId?: string): Promise<void>;
71 closeNotebookDocument(notebookUri: string): Promise<void>;
72 getCodeActions(documentUri: string, range: IRange): Promise<ICodeAction[]>;
73 getCompletions(
74 documentUri: string,
75 position: IPosition,
76 ): Promise<ICompletionList>;
77 getFormatChanges(documentUri: string): Promise<ITextEdit[]>;
78 getHover(
79 documentUri: string,
80 position: IPosition,
81 ): Promise<IHover | undefined>;
82 getDefinition(
83 documentUri: string,
84 position: IPosition,
85 ): Promise<ILocation | undefined>;
86 getReferences(
87 documentUri: string,
88 position: IPosition,
89 includeDeclaration: boolean,
90 ): Promise<ILocation[]>;
91 getSignatureHelp(
92 documentUri: string,
93 position: IPosition,
94 ): Promise<ISignatureHelp | undefined>;
95 getRename(
96 documentUri: string,
97 position: IPosition,
98 newName: string,
99 ): Promise<IWorkspaceEdit | undefined>;
100 prepareRename(
101 documentUri: string,
102 position: IPosition,
103 ): Promise<ITextEdit | undefined>;
104 getCodeLenses(documentUri: string): Promise<ICodeLens[]>;
105
106 dispose(): Promise<void>;
107
108 addEventListener<T extends LanguageServiceEvent["type"]>(
109 type: T,
110 listener: (event: Extract<LanguageServiceEvent, { type: T }>) => void,
111 ): void;
112
113 removeEventListener<T extends LanguageServiceEvent["type"]>(
114 type: T,
115 listener: (event: Extract<LanguageServiceEvent, { type: T }>) => void,
116 ): void;
117}
118
119export const qsharpLibraryUriScheme = "qsharp-library-source";
120export const qsharpGithubUriScheme = "qsharp-github-source";
121
122export type ILanguageServiceWorker = ILanguageService & IServiceProxy;
123
124export class QSharpLanguageService implements ILanguageService {
125 private languageService: LanguageService;
126 private eventHandler =
127 new EventTarget() as IServiceEventTarget<LanguageServiceEvent>;
128
129 private backgroundWork: Promise<void>;
130
131 constructor(
132 private wasm: QscWasm,
133 host: IProjectHost = {
134 readFile: async () => null,
135 listDirectory: async () => [],
136 resolvePath: async () => null,
137 fetchGithub: async () => "",
138 findManifestDirectory: async () => null,
139 },
140 ) {
141 log.info("Constructing a QSharpLanguageService instance");
142 this.languageService = new wasm.LanguageService();
143
144 this.backgroundWork = this.languageService.start_background_work(
145 this.onDiagnostics.bind(this),
146 this.onTestCallables.bind(this),
147 host,
148 );
149 }
150
151 async updateConfiguration(config: IWorkspaceConfiguration): Promise<void> {
152 this.languageService.update_configuration(config);
153 }
154
155 async updateDocument(
156 documentUri: string,
157 version: number,
158 code: string,
159 languageId?: string,
160 ): Promise<void> {
161 this.languageService.update_document(
162 documentUri,
163 version,
164 code,
165 languageId || "qsharp",
166 );
167 }
168
169 async updateNotebookDocument(
170 notebookUri: string,
171 version: number,
172 metadata: INotebookMetadata,
173 cells: { uri: string; version: number; code: string }[],
174 ): Promise<void> {
175 this.languageService.update_notebook_document(notebookUri, metadata, cells);
176 }
177
178 async closeDocument(documentUri: string, languageId?: string): Promise<void> {
179 this.languageService.close_document(documentUri, languageId || "qsharp");
180 }
181
182 async closeNotebookDocument(documentUri: string): Promise<void> {
183 this.languageService.close_notebook_document(documentUri);
184 }
185
186 async getCodeActions(
187 documentUri: string,
188 range: IRange,
189 ): Promise<ICodeAction[]> {
190 return this.languageService.get_code_actions(documentUri, range);
191 }
192
193 async getCompletions(
194 documentUri: string,
195 position: IPosition,
196 ): Promise<ICompletionList> {
197 // Tiny delay to let the compilation catch up before we invoke
198 // the completion provider.
199 // This becomes important when the completion list is triggered
200 // during typing. If the last character typed is significant to
201 // the completion (e.g. in `Foo.` completions)
202 // it's critical that the completion provider "sees" this character.
203 await new Promise((resolve) => setTimeout(resolve, 50));
204 return this.languageService.get_completions(documentUri, position);
205 }
206
207 async getFormatChanges(documentUri: string): Promise<ITextEdit[]> {
208 return this.languageService.get_format_changes(documentUri);
209 }
210
211 async getHover(
212 documentUri: string,
213 position: IPosition,
214 ): Promise<IHover | undefined> {
215 return this.languageService.get_hover(documentUri, position);
216 }
217
218 async getDefinition(
219 documentUri: string,
220 position: IPosition,
221 ): Promise<ILocation | undefined> {
222 return this.languageService.get_definition(documentUri, position);
223 }
224
225 async getReferences(
226 documentUri: string,
227 position: IPosition,
228 includeDeclaration: boolean,
229 ): Promise<ILocation[]> {
230 return this.languageService.get_references(
231 documentUri,
232 position,
233 includeDeclaration,
234 );
235 }
236
237 async getSignatureHelp(
238 documentUri: string,
239 position: IPosition,
240 ): Promise<ISignatureHelp | undefined> {
241 return this.languageService.get_signature_help(documentUri, position);
242 }
243
244 async getRename(
245 documentUri: string,
246 position: IPosition,
247 newName: string,
248 ): Promise<IWorkspaceEdit | undefined> {
249 return this.languageService.get_rename(documentUri, position, newName);
250 }
251
252 async prepareRename(
253 documentUri: string,
254 position: IPosition,
255 ): Promise<ITextEdit | undefined> {
256 return this.languageService.prepare_rename(documentUri, position);
257 }
258
259 async getCodeLenses(documentUri: string): Promise<ICodeLens[]> {
260 return this.languageService.get_code_lenses(documentUri);
261 }
262
263 async dispose() {
264 this.languageService.stop_background_work();
265 await this.backgroundWork;
266 this.languageService.free();
267 }
268
269 addEventListener<T extends LanguageServiceEvent["type"]>(
270 type: T,
271 listener: (event: Extract<LanguageServiceEvent, { type: T }>) => void,
272 ) {
273 this.eventHandler.addEventListener(type, listener);
274 }
275
276 removeEventListener<T extends LanguageServiceEvent["type"]>(
277 type: T,
278 listener: (event: Extract<LanguageServiceEvent, { type: T }>) => void,
279 ) {
280 this.eventHandler.removeEventListener(type, listener);
281 }
282
283 async onDiagnostics(
284 uri: string,
285 version: number | undefined,
286 diagnostics: VSDiagnostic[],
287 ) {
288 try {
289 const event = new Event("diagnostics") as LanguageServiceDiagnosticEvent &
290 Event;
291 event.detail = {
292 uri,
293 version: version ?? 0,
294 diagnostics,
295 };
296 this.eventHandler.dispatchEvent(event);
297 } catch (e) {
298 log.error("Error in onDiagnostics", e);
299 }
300 }
301
302 async onTestCallables(callables: ITestDescriptor[]) {
303 try {
304 const event = new Event(
305 "testCallables",
306 ) as LanguageServiceTestCallablesEvent & Event;
307 event.detail = {
308 callables,
309 };
310 this.eventHandler.dispatchEvent(event);
311 } catch (e) {
312 log.error("Error in onTestCallables", e);
313 }
314 }
315}
316
317/**
318 * The protocol definition to allow running the language service in a worker.
319 *
320 * Not to be confused with "the" LSP (Language Server Protocol).
321 */
322export const languageServiceProtocol: ServiceProtocol<
323 ILanguageService,
324 LanguageServiceDiagnosticEvent
325> = {
326 class: QSharpLanguageService,
327 methods: {
328 updateConfiguration: "request",
329 updateDocument: "request",
330 updateNotebookDocument: "request",
331 closeDocument: "request",
332 closeNotebookDocument: "request",
333 getCodeActions: "request",
334 getCompletions: "request",
335 getFormatChanges: "request",
336 getHover: "request",
337 getDefinition: "request",
338 getReferences: "request",
339 getSignatureHelp: "request",
340 getRename: "request",
341 prepareRename: "request",
342 getCodeLenses: "request",
343 dispose: "request",
344 addEventListener: "addEventListener",
345 removeEventListener: "removeEventListener",
346 },
347 eventNames: ["diagnostics"],
348};
349