aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src')
-rw-r--r--editors/code/src/extension.ts400
1 files changed, 400 insertions, 0 deletions
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
new file mode 100644
index 000000000..2c42c07fe
--- /dev/null
+++ b/editors/code/src/extension.ts
@@ -0,0 +1,400 @@
1'use strict';
2import * as vscode from 'vscode';
3import * as lc from 'vscode-languageclient'
4import { DH_UNABLE_TO_CHECK_GENERATOR } from 'constants';
5
6
7let client: lc.LanguageClient;
8
9let uris = {
10 syntaxTree: vscode.Uri.parse('ra-lsp://syntaxtree')
11}
12
13
14export function activate(context: vscode.ExtensionContext) {
15 let textDocumentContentProvider = new TextDocumentContentProvider()
16 let dispose = (disposable: vscode.Disposable) => {
17 context.subscriptions.push(disposable);
18 }
19 let registerCommand = (name: string, f: any) => {
20 dispose(vscode.commands.registerCommand(name, f))
21 }
22
23 registerCommand('ra-lsp.syntaxTree', () => openDoc(uris.syntaxTree))
24 registerCommand('ra-lsp.extendSelection', async () => {
25 let editor = vscode.window.activeTextEditor
26 if (editor == null || editor.document.languageId != "rust") return
27 let request: ExtendSelectionParams = {
28 textDocument: { uri: editor.document.uri.toString() },
29 selections: editor.selections.map((s) => {
30 return client.code2ProtocolConverter.asRange(s)
31 })
32 }
33 let response = await client.sendRequest<ExtendSelectionResult>("m/extendSelection", request)
34 editor.selections = response.selections.map((range) => {
35 let r = client.protocol2CodeConverter.asRange(range)
36 return new vscode.Selection(r.start, r.end)
37 })
38 })
39 registerCommand('ra-lsp.matchingBrace', async () => {
40 let editor = vscode.window.activeTextEditor
41 if (editor == null || editor.document.languageId != "rust") return
42 let request: FindMatchingBraceParams = {
43 textDocument: { uri: editor.document.uri.toString() },
44 offsets: editor.selections.map((s) => {
45 return client.code2ProtocolConverter.asPosition(s.active)
46 })
47 }
48 let response = await client.sendRequest<lc.Position[]>("m/findMatchingBrace", request)
49 editor.selections = editor.selections.map((sel, idx) => {
50 let active = client.protocol2CodeConverter.asPosition(response[idx])
51 let anchor = sel.isEmpty ? active : sel.anchor
52 return new vscode.Selection(anchor, active)
53 })
54 editor.revealRange(editor.selection)
55 })
56 registerCommand('ra-lsp.joinLines', async () => {
57 let editor = vscode.window.activeTextEditor
58 if (editor == null || editor.document.languageId != "rust") return
59 let request: JoinLinesParams = {
60 textDocument: { uri: editor.document.uri.toString() },
61 range: client.code2ProtocolConverter.asRange(editor.selection),
62 }
63 let change = await client.sendRequest<SourceChange>("m/joinLines", request)
64 await applySourceChange(change)
65 })
66 registerCommand('ra-lsp.parentModule', async () => {
67 let editor = vscode.window.activeTextEditor
68 if (editor == null || editor.document.languageId != "rust") return
69 let request: lc.TextDocumentIdentifier = {
70 uri: editor.document.uri.toString()
71 }
72 let response = await client.sendRequest<lc.Location[]>("m/parentModule", request)
73 let loc = response[0]
74 if (loc == null) return
75 let uri = client.protocol2CodeConverter.asUri(loc.uri)
76 let range = client.protocol2CodeConverter.asRange(loc.range)
77
78 let doc = await vscode.workspace.openTextDocument(uri)
79 let e = await vscode.window.showTextDocument(doc)
80 e.revealRange(range, vscode.TextEditorRevealType.InCenter)
81 })
82
83 let prevRunnable: RunnableQuickPick | undefined = undefined
84 registerCommand('ra-lsp.run', async () => {
85 let editor = vscode.window.activeTextEditor
86 if (editor == null || editor.document.languageId != "rust") return
87 let textDocument: lc.TextDocumentIdentifier = {
88 uri: editor.document.uri.toString()
89 }
90 let params: RunnablesParams = {
91 textDocument,
92 position: client.code2ProtocolConverter.asPosition(editor.selection.active)
93 }
94 let runnables = await client.sendRequest<Runnable[]>('m/runnables', params)
95 let items: RunnableQuickPick[] = []
96 if (prevRunnable) {
97 items.push(prevRunnable)
98 }
99 for (let r of runnables) {
100 if (prevRunnable && JSON.stringify(prevRunnable.runnable) == JSON.stringify(r)) {
101 continue
102 }
103 items.push(new RunnableQuickPick(r))
104 }
105 let item = await vscode.window.showQuickPick(items)
106 if (item) {
107 item.detail = "rerun"
108 prevRunnable = item
109 let task = createTask(item.runnable)
110 return await vscode.tasks.executeTask(task)
111 }
112 })
113 registerCommand('ra-lsp.applySourceChange', applySourceChange)
114
115 dispose(vscode.workspace.registerTextDocumentContentProvider(
116 'ra-lsp',
117 textDocumentContentProvider
118 ))
119 startServer()
120 vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => {
121 let doc = event.document
122 if (doc.languageId != "rust") return
123 afterLs(() => {
124 textDocumentContentProvider.eventEmitter.fire(uris.syntaxTree)
125 })
126 }, null, context.subscriptions)
127 vscode.window.onDidChangeActiveTextEditor(async (editor) => {
128 if (!editor || editor.document.languageId != 'rust') return
129 let params: lc.TextDocumentIdentifier = {
130 uri: editor.document.uri.toString()
131 }
132 let decorations = await client.sendRequest<Decoration[]>("m/decorationsRequest", params)
133 setHighlights(editor, decorations)
134 })
135}
136
137// We need to order this after LS updates, but there's no API for that.
138// Hence, good old setTimeout.
139function afterLs(f: () => any) {
140 setTimeout(f, 10)
141}
142
143export function deactivate(): Thenable<void> {
144 if (!client) {
145 return Promise.resolve();
146 }
147 return client.stop();
148}
149
150function startServer() {
151 let run: lc.Executable = {
152 command: "ra_lsp_server",
153 options: { cwd: "." }
154 }
155 let serverOptions: lc.ServerOptions = {
156 run,
157 debug: run
158 };
159
160 let clientOptions: lc.LanguageClientOptions = {
161 documentSelector: [{ scheme: 'file', language: 'rust' }],
162 };
163
164 client = new lc.LanguageClient(
165 'ra-lsp',
166 'rust-analyzer languge server',
167 serverOptions,
168 clientOptions,
169 );
170 client.onReady().then(() => {
171 client.onNotification(
172 "m/publishDecorations",
173 (params: PublishDecorationsParams) => {
174 let editor = vscode.window.visibleTextEditors.find(
175 (editor) => editor.document.uri.toString() == params.uri
176 )
177 if (editor == null) return;
178 setHighlights(
179 editor,
180 params.decorations,
181 )
182 }
183 )
184 })
185 client.start();
186}
187
188async function openDoc(uri: vscode.Uri) {
189 let document = await vscode.workspace.openTextDocument(uri)
190 return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true)
191}
192
193class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
194 public eventEmitter = new vscode.EventEmitter<vscode.Uri>()
195 public syntaxTree: string = "Not available"
196
197 public provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> {
198 let editor = vscode.window.activeTextEditor;
199 if (editor == null) return ""
200 let request: SyntaxTreeParams = {
201 textDocument: { uri: editor.document.uri.toString() }
202 };
203 return client.sendRequest<SyntaxTreeResult>("m/syntaxTree", request);
204 }
205
206 get onDidChange(): vscode.Event<vscode.Uri> {
207 return this.eventEmitter.event
208 }
209}
210
211
212const decorations: { [index: string]: vscode.TextEditorDecorationType } = (() => {
213 const decor = (obj: any) => vscode.window.createTextEditorDecorationType({ color: obj })
214 return {
215 background: decor("#3F3F3F"),
216 error: vscode.window.createTextEditorDecorationType({
217 borderColor: "red",
218 borderStyle: "none none dashed none",
219 }),
220 comment: decor("#7F9F7F"),
221 string: decor("#CC9393"),
222 keyword: decor("#F0DFAF"),
223 function: decor("#93E0E3"),
224 parameter: decor("#94BFF3"),
225 builtin: decor("#DD6718"),
226 text: decor("#DCDCCC"),
227 attribute: decor("#BFEBBF"),
228 literal: decor("#DFAF8F"),
229 }
230})()
231
232function setHighlights(
233 editor: vscode.TextEditor,
234 highlihgs: Array<Decoration>
235) {
236 let byTag: Map<string, vscode.Range[]> = new Map()
237 for (let tag in decorations) {
238 byTag.set(tag, [])
239 }
240
241 for (let d of highlihgs) {
242 if (!byTag.get(d.tag)) {
243 console.log(`unknown tag ${d.tag}`)
244 continue
245 }
246 byTag.get(d.tag)!.push(
247 client.protocol2CodeConverter.asRange(d.range)
248 )
249 }
250
251 for (let tag of byTag.keys()) {
252 let dec: vscode.TextEditorDecorationType = decorations[tag]
253 let ranges = byTag.get(tag)!
254 editor.setDecorations(dec, ranges)
255 }
256}
257
258interface SyntaxTreeParams {
259 textDocument: lc.TextDocumentIdentifier;
260}
261
262type SyntaxTreeResult = string
263
264interface ExtendSelectionParams {
265 textDocument: lc.TextDocumentIdentifier;
266 selections: lc.Range[];
267}
268
269interface ExtendSelectionResult {
270 selections: lc.Range[];
271}
272
273interface FindMatchingBraceParams {
274 textDocument: lc.TextDocumentIdentifier;
275 offsets: lc.Position[];
276}
277
278interface JoinLinesParams {
279 textDocument: lc.TextDocumentIdentifier;
280 range: lc.Range;
281}
282
283interface PublishDecorationsParams {
284 uri: string,
285 decorations: Decoration[],
286}
287
288interface RunnablesParams {
289 textDocument: lc.TextDocumentIdentifier,
290 position?: lc.Position,
291}
292
293interface Runnable {
294 range: lc.Range;
295 label: string;
296 bin: string;
297 args: string[];
298 env: { [index: string]: string },
299}
300
301class RunnableQuickPick implements vscode.QuickPickItem {
302 label: string;
303 description?: string | undefined;
304 detail?: string | undefined;
305 picked?: boolean | undefined;
306
307 constructor(public runnable: Runnable) {
308 this.label = runnable.label
309 }
310}
311
312interface Decoration {
313 range: lc.Range,
314 tag: string,
315}
316
317
318interface CargoTaskDefinition extends vscode.TaskDefinition {
319 type: 'cargo';
320 label: string;
321 command: string;
322 args: Array<string>;
323 env?: { [key: string]: string };
324}
325
326function createTask(spec: Runnable): vscode.Task {
327 const TASK_SOURCE = 'Rust';
328 let definition: CargoTaskDefinition = {
329 type: 'cargo',
330 label: 'cargo',
331 command: spec.bin,
332 args: spec.args,
333 env: spec.env
334 }
335
336 let execCmd = `${definition.command} ${definition.args.join(' ')}`;
337 let execOption: vscode.ShellExecutionOptions = {
338 cwd: '.',
339 env: definition.env,
340 };
341 let exec = new vscode.ShellExecution(`clear; ${execCmd}`, execOption);
342
343 let f = vscode.workspace.workspaceFolders![0]
344 let t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']);
345 return t;
346}
347
348interface FileSystemEdit {
349 type: string;
350 uri?: string;
351 src?: string;
352 dst?: string;
353}
354
355interface SourceChange {
356 label: string,
357 sourceFileEdits: lc.TextDocumentEdit[],
358 fileSystemEdits: FileSystemEdit[],
359 cursorPosition?: lc.TextDocumentPositionParams,
360}
361
362async function applySourceChange(change: SourceChange) {
363 console.log(`applySOurceChange ${JSON.stringify(change)}`)
364 let wsEdit = new vscode.WorkspaceEdit()
365 for (let sourceEdit of change.sourceFileEdits) {
366 let uri = client.protocol2CodeConverter.asUri(sourceEdit.textDocument.uri)
367 let edits = client.protocol2CodeConverter.asTextEdits(sourceEdit.edits)
368 wsEdit.set(uri, edits)
369 }
370 let created;
371 let moved;
372 for (let fsEdit of change.fileSystemEdits) {
373 if (fsEdit.type == "createFile") {
374 let uri = vscode.Uri.parse(fsEdit.uri!)
375 wsEdit.createFile(uri)
376 created = uri
377 } else if (fsEdit.type == "moveFile") {
378 let src = vscode.Uri.parse(fsEdit.src!)
379 let dst = vscode.Uri.parse(fsEdit.dst!)
380 wsEdit.renameFile(src, dst)
381 moved = dst
382 } else {
383 console.error(`unknown op: ${JSON.stringify(fsEdit)}`)
384 }
385 }
386 let toOpen = created || moved
387 let toReveal = change.cursorPosition
388 await vscode.workspace.applyEdit(wsEdit)
389 if (toOpen) {
390 let doc = await vscode.workspace.openTextDocument(toOpen)
391 await vscode.window.showTextDocument(doc)
392 } else if (toReveal) {
393 let uri = client.protocol2CodeConverter.asUri(toReveal.textDocument.uri)
394 let position = client.protocol2CodeConverter.asPosition(toReveal.position)
395 let editor = vscode.window.activeTextEditor;
396 if (!editor || editor.document.uri.toString() != uri.toString()) return
397 if (!editor.selection.isEmpty) return
398 editor!.selection = new vscode.Selection(position, position)
399 }
400}