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