aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editors/code/src/commands/apply_source_change.ts58
-rw-r--r--editors/code/src/commands/extend_selection.ts29
-rw-r--r--editors/code/src/commands/index.ts17
-rw-r--r--editors/code/src/commands/join_lines.ts21
-rw-r--r--editors/code/src/commands/matching_brace.ts27
-rw-r--r--editors/code/src/commands/parent_module.ts22
-rw-r--r--editors/code/src/commands/runnables.ts88
-rw-r--r--editors/code/src/commands/syntaxTree.ts38
-rw-r--r--editors/code/src/config.ts23
-rw-r--r--editors/code/src/events/change_active_text_editor.ts14
-rw-r--r--editors/code/src/events/change_text_document.ts19
-rw-r--r--editors/code/src/events/index.ts7
-rw-r--r--editors/code/src/extension.ts452
-rw-r--r--editors/code/src/highlighting.ts82
-rw-r--r--editors/code/src/notifications/index.ts5
-rw-r--r--editors/code/src/notifications/publish_decorations.ts20
-rw-r--r--editors/code/src/server.ts37
-rw-r--r--editors/code/tslint.json13
18 files changed, 557 insertions, 415 deletions
diff --git a/editors/code/src/commands/apply_source_change.ts b/editors/code/src/commands/apply_source_change.ts
new file mode 100644
index 000000000..67765e5a3
--- /dev/null
+++ b/editors/code/src/commands/apply_source_change.ts
@@ -0,0 +1,58 @@
1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient';
3
4import { Server } from '../server';
5
6interface FileSystemEdit {
7 type: string;
8 uri?: string;
9 src?: string;
10 dst?: string;
11}
12
13export interface SourceChange {
14 label: string;
15 sourceFileEdits: lc.TextDocumentEdit[];
16 fileSystemEdits: FileSystemEdit[];
17 cursorPosition?: lc.TextDocumentPositionParams;
18}
19
20export async function handle(change: SourceChange) {
21 const wsEdit = new vscode.WorkspaceEdit();
22 for (const sourceEdit of change.sourceFileEdits) {
23 const uri = Server.client.protocol2CodeConverter.asUri(sourceEdit.textDocument.uri);
24 const edits = Server.client.protocol2CodeConverter.asTextEdits(sourceEdit.edits);
25 wsEdit.set(uri, edits);
26 }
27 let created;
28 let moved;
29 for (const fsEdit of change.fileSystemEdits) {
30 switch (fsEdit.type) {
31 case 'createFile':
32 const uri = vscode.Uri.parse(fsEdit.uri!);
33 wsEdit.createFile(uri);
34 created = uri;
35 break;
36 case 'moveFile':
37 const src = vscode.Uri.parse(fsEdit.src!);
38 const dst = vscode.Uri.parse(fsEdit.dst!);
39 wsEdit.renameFile(src, dst);
40 moved = dst;
41 break;
42 }
43 }
44 const toOpen = created || moved;
45 const toReveal = change.cursorPosition;
46 await vscode.workspace.applyEdit(wsEdit);
47 if (toOpen) {
48 const doc = await vscode.workspace.openTextDocument(toOpen);
49 await vscode.window.showTextDocument(doc);
50 } else if (toReveal) {
51 const uri = Server.client.protocol2CodeConverter.asUri(toReveal.textDocument.uri);
52 const position = Server.client.protocol2CodeConverter.asPosition(toReveal.position);
53 const editor = vscode.window.activeTextEditor;
54 if (!editor || editor.document.uri.toString() !== uri.toString()) { return; }
55 if (!editor.selection.isEmpty) { return; }
56 editor!.selection = new vscode.Selection(position, position);
57 }
58}
diff --git a/editors/code/src/commands/extend_selection.ts b/editors/code/src/commands/extend_selection.ts
new file mode 100644
index 000000000..cdc3d10fb
--- /dev/null
+++ b/editors/code/src/commands/extend_selection.ts
@@ -0,0 +1,29 @@
1import * as vscode from 'vscode';
2
3import { Range, TextDocumentIdentifier } from 'vscode-languageclient';
4import { Server } from '../server';
5
6interface ExtendSelectionParams {
7 textDocument: TextDocumentIdentifier;
8 selections: Range[];
9}
10
11interface ExtendSelectionResult {
12 selections: Range[];
13}
14
15export async function handle() {
16 const editor = vscode.window.activeTextEditor;
17 if (editor == null || editor.document.languageId !== 'rust') { return; }
18 const request: ExtendSelectionParams = {
19 selections: editor.selections.map((s) => {
20 return Server.client.code2ProtocolConverter.asRange(s);
21 }),
22 textDocument: { uri: editor.document.uri.toString() },
23 };
24 const response = await Server.client.sendRequest<ExtendSelectionResult>('m/extendSelection', request);
25 editor.selections = response.selections.map((range: Range) => {
26 const r = Server.client.protocol2CodeConverter.asRange(range);
27 return new vscode.Selection(r.start, r.end);
28 });
29}
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
new file mode 100644
index 000000000..dfdcd6454
--- /dev/null
+++ b/editors/code/src/commands/index.ts
@@ -0,0 +1,17 @@
1import * as applySourceChange from './apply_source_change';
2import * as extendSelection from './extend_selection';
3import * as joinLines from './join_lines';
4import * as matchingBrace from './matching_brace';
5import * as parentModule from './parent_module';
6import * as runnables from './runnables';
7import * as syntaxTree from './syntaxTree';
8
9export {
10 applySourceChange,
11 extendSelection,
12 joinLines,
13 matchingBrace,
14 parentModule,
15 runnables,
16 syntaxTree,
17};
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts
new file mode 100644
index 000000000..526b698cc
--- /dev/null
+++ b/editors/code/src/commands/join_lines.ts
@@ -0,0 +1,21 @@
1import * as vscode from 'vscode';
2
3import { Range, TextDocumentIdentifier } from 'vscode-languageclient';
4import { Server } from '../server';
5import { handle as applySourceChange, SourceChange } from './apply_source_change';
6
7interface JoinLinesParams {
8 textDocument: TextDocumentIdentifier;
9 range: Range;
10}
11
12export async function handle() {
13 const editor = vscode.window.activeTextEditor;
14 if (editor == null || editor.document.languageId !== 'rust') { return; }
15 const request: JoinLinesParams = {
16 range: Server.client.code2ProtocolConverter.asRange(editor.selection),
17 textDocument: { uri: editor.document.uri.toString() },
18 };
19 const change = await Server.client.sendRequest<SourceChange>('m/joinLines', request);
20 await applySourceChange(change);
21}
diff --git a/editors/code/src/commands/matching_brace.ts b/editors/code/src/commands/matching_brace.ts
new file mode 100644
index 000000000..a80446a8f
--- /dev/null
+++ b/editors/code/src/commands/matching_brace.ts
@@ -0,0 +1,27 @@
1import * as vscode from 'vscode';
2
3import { Position, TextDocumentIdentifier } from 'vscode-languageclient';
4import { Server } from '../server';
5
6interface FindMatchingBraceParams {
7 textDocument: TextDocumentIdentifier;
8 offsets: Position[];
9}
10
11export async function handle() {
12 const editor = vscode.window.activeTextEditor;
13 if (editor == null || editor.document.languageId !== 'rust') { return; }
14 const request: FindMatchingBraceParams = {
15 textDocument: { uri: editor.document.uri.toString() },
16 offsets: editor.selections.map((s) => {
17 return Server.client.code2ProtocolConverter.asPosition(s.active);
18 }),
19 };
20 const response = await Server.client.sendRequest<Position[]>('m/findMatchingBrace', request);
21 editor.selections = editor.selections.map((sel, idx) => {
22 const active = Server.client.protocol2CodeConverter.asPosition(response[idx]);
23 const anchor = sel.isEmpty ? active : sel.anchor;
24 return new vscode.Selection(anchor, active);
25 });
26 editor.revealRange(editor.selection);
27}
diff --git a/editors/code/src/commands/parent_module.ts b/editors/code/src/commands/parent_module.ts
new file mode 100644
index 000000000..d66fb3026
--- /dev/null
+++ b/editors/code/src/commands/parent_module.ts
@@ -0,0 +1,22 @@
1import * as vscode from 'vscode';
2
3import { Location, TextDocumentIdentifier } from 'vscode-languageclient';
4import { Server } from '../server';
5
6export async function handle() {
7 const editor = vscode.window.activeTextEditor;
8 if (editor == null || editor.document.languageId !== 'rust') { return; }
9 const request: TextDocumentIdentifier = {
10 uri: editor.document.uri.toString(),
11 };
12 const response = await Server.client.sendRequest<Location[]>('m/parentModule', request);
13 const loc = response[0];
14 if (loc == null) { return; }
15 const uri = Server.client.protocol2CodeConverter.asUri(loc.uri);
16 const range = Server.client.protocol2CodeConverter.asRange(loc.range);
17
18 const doc = await vscode.workspace.openTextDocument(uri);
19 const e = await vscode.window.showTextDocument(doc);
20 e.selection = new vscode.Selection(range.start, range.start);
21 e.revealRange(range, vscode.TextEditorRevealType.InCenter);
22}
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
new file mode 100644
index 000000000..40f590dce
--- /dev/null
+++ b/editors/code/src/commands/runnables.ts
@@ -0,0 +1,88 @@
1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient';
3import { Server } from '../server';
4
5interface RunnablesParams {
6 textDocument: lc.TextDocumentIdentifier;
7 position?: lc.Position;
8}
9
10interface Runnable {
11 range: lc.Range;
12 label: string;
13 bin: string;
14 args: string[];
15 env: { [index: string]: string };
16}
17
18class RunnableQuickPick implements vscode.QuickPickItem {
19 public label: string;
20 public description?: string | undefined;
21 public detail?: string | undefined;
22 public picked?: boolean | undefined;
23
24 constructor(public runnable: Runnable) {
25 this.label = runnable.label;
26 }
27}
28
29interface CargoTaskDefinition extends vscode.TaskDefinition {
30 type: 'cargo';
31 label: string;
32 command: string;
33 args: string[];
34 env?: { [key: string]: string };
35}
36
37function createTask(spec: Runnable): vscode.Task {
38 const TASK_SOURCE = 'Rust';
39 const definition: CargoTaskDefinition = {
40 type: 'cargo',
41 label: 'cargo',
42 command: spec.bin,
43 args: spec.args,
44 env: spec.env,
45 };
46
47 const execCmd = `${definition.command} ${definition.args.join(' ')}`;
48 const execOption: vscode.ShellExecutionOptions = {
49 cwd: '.',
50 env: definition.env,
51 };
52 const exec = new vscode.ShellExecution(`clear; ${execCmd}`, execOption);
53
54 const f = vscode.workspace.workspaceFolders![0];
55 const t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']);
56 return t;
57}
58
59let prevRunnable: RunnableQuickPick | undefined;
60export async function handle() {
61 const editor = vscode.window.activeTextEditor;
62 if (editor == null || editor.document.languageId !== 'rust') { return; }
63 const textDocument: lc.TextDocumentIdentifier = {
64 uri: editor.document.uri.toString(),
65 };
66 const params: RunnablesParams = {
67 textDocument,
68 position: Server.client.code2ProtocolConverter.asPosition(editor.selection.active),
69 };
70 const runnables = await Server.client.sendRequest<Runnable[]>('m/runnables', params);
71 const items: RunnableQuickPick[] = [];
72 if (prevRunnable) {
73 items.push(prevRunnable);
74 }
75 for (const r of runnables) {
76 if (prevRunnable && JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)) {
77 continue;
78 }
79 items.push(new RunnableQuickPick(r));
80 }
81 const item = await vscode.window.showQuickPick(items);
82 if (item) {
83 item.detail = 'rerun';
84 prevRunnable = item;
85 const task = createTask(item.runnable);
86 return await vscode.tasks.executeTask(task);
87 }
88}
diff --git a/editors/code/src/commands/syntaxTree.ts b/editors/code/src/commands/syntaxTree.ts
new file mode 100644
index 000000000..dcb721eee
--- /dev/null
+++ b/editors/code/src/commands/syntaxTree.ts
@@ -0,0 +1,38 @@
1import * as vscode from 'vscode';
2import { TextDocumentIdentifier } from 'vscode-languageclient';
3
4import { Server } from '../server';
5
6export const syntaxTreeUri = vscode.Uri.parse('ra-lsp://syntaxtree');
7
8export class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
9 public eventEmitter = new vscode.EventEmitter<vscode.Uri>();
10 public syntaxTree: string = 'Not available';
11
12 public provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> {
13 const editor = vscode.window.activeTextEditor;
14 if (editor == null) { return ''; }
15 const request: SyntaxTreeParams = {
16 textDocument: { uri: editor.document.uri.toString() },
17 };
18 return Server.client.sendRequest<SyntaxTreeResult>('m/syntaxTree', request);
19 }
20
21 get onDidChange(): vscode.Event<vscode.Uri> {
22 return this.eventEmitter.event;
23 }
24}
25
26interface SyntaxTreeParams {
27 textDocument: TextDocumentIdentifier;
28}
29
30type SyntaxTreeResult = string;
31
32// Opens the virtual file that will show the syntax tree
33//
34// The contents of the file come from the `TextDocumentContentProvider`
35export async function handle() {
36 const document = await vscode.workspace.openTextDocument(syntaxTreeUri);
37 return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
38}
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
new file mode 100644
index 000000000..740b5be20
--- /dev/null
+++ b/editors/code/src/config.ts
@@ -0,0 +1,23 @@
1import * as vscode from 'vscode';
2
3import { Server } from './server';
4
5export class Config {
6 public highlightingOn = true;
7
8 constructor() {
9 vscode.workspace.onDidChangeConfiguration((_) => this.userConfigChanged());
10 this.userConfigChanged();
11 }
12
13 public userConfigChanged() {
14 const config = vscode.workspace.getConfiguration('ra-lsp');
15 if (config.has('highlightingOn')) {
16 this.highlightingOn = config.get('highlightingOn') as boolean;
17 }
18
19 if (!this.highlightingOn && Server) {
20 Server.highlighter.removeHighlights();
21 }
22 }
23}
diff --git a/editors/code/src/events/change_active_text_editor.ts b/editors/code/src/events/change_active_text_editor.ts
new file mode 100644
index 000000000..3440aa0c3
--- /dev/null
+++ b/editors/code/src/events/change_active_text_editor.ts
@@ -0,0 +1,14 @@
1import { TextEditor } from 'vscode';
2import { TextDocumentIdentifier } from 'vscode-languageclient';
3
4import { Decoration } from '../highlighting';
5import { Server } from '../server';
6
7export async function handle(editor: TextEditor | undefined) {
8 if (!Server.config.highlightingOn || !editor || editor.document.languageId !== 'rust') { return; }
9 const params: TextDocumentIdentifier = {
10 uri: editor.document.uri.toString(),
11 };
12 const decorations = await Server.client.sendRequest<Decoration[]>('m/decorationsRequest', params);
13 Server.highlighter.setHighlights(editor, decorations);
14}
diff --git a/editors/code/src/events/change_text_document.ts b/editors/code/src/events/change_text_document.ts
new file mode 100644
index 000000000..b3000e026
--- /dev/null
+++ b/editors/code/src/events/change_text_document.ts
@@ -0,0 +1,19 @@
1import * as vscode from 'vscode';
2
3import { syntaxTreeUri, TextDocumentContentProvider } from '../commands/syntaxTree';
4
5export function createHandler(textDocumentContentProvider: TextDocumentContentProvider) {
6 return (event: vscode.TextDocumentChangeEvent) => {
7 const doc = event.document;
8 if (doc.languageId !== 'rust') { return; }
9 afterLs(() => {
10 textDocumentContentProvider.eventEmitter.fire(syntaxTreeUri);
11 });
12 };
13}
14
15// We need to order this after LS updates, but there's no API for that.
16// Hence, good old setTimeout.
17function afterLs(f: () => any) {
18 setTimeout(f, 10);
19}
diff --git a/editors/code/src/events/index.ts b/editors/code/src/events/index.ts
new file mode 100644
index 000000000..b570a7a92
--- /dev/null
+++ b/editors/code/src/events/index.ts
@@ -0,0 +1,7 @@
1import * as changeActiveTextEditor from './change_active_text_editor';
2import * as changeTextDocument from './change_text_document';
3
4export {
5 changeActiveTextEditor,
6 changeTextDocument,
7};
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
index fde6a480d..44e74f4cc 100644
--- a/editors/code/src/extension.ts
+++ b/editors/code/src/extension.ts
@@ -1,434 +1,56 @@
1'use strict';
2import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
3import * as lc from 'vscode-languageclient' 2import * as lc from 'vscode-languageclient';
4 3
5let client: lc.LanguageClient; 4import * as commands from './commands';
6 5import { TextDocumentContentProvider } from './commands/syntaxTree';
7let uris = { 6import * as events from './events';
8 syntaxTree: vscode.Uri.parse('ra-lsp://syntaxtree') 7import * as notifications from './notifications';
9} 8import { Server } from './server';
10
11let highlightingOn = true;
12 9
13export function activate(context: vscode.ExtensionContext) { 10export function activate(context: vscode.ExtensionContext) {
14 let applyHighlightingOn = () => { 11 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); 12 context.subscriptions.push(disposable);
34 } 13 }
35 let registerCommand = (name: string, f: any) => { 14
36 dispose(vscode.commands.registerCommand(name, f)) 15 function registerCommand(name: string, f: any) {
16 disposeOnDeactivation(vscode.commands.registerCommand(name, f));
37 } 17 }
38 18
39 registerCommand('ra-lsp.syntaxTree', () => openDoc(uris.syntaxTree)) 19 // Commands are requests from vscode to the language server
40 registerCommand('ra-lsp.extendSelection', async () => { 20 registerCommand('ra-lsp.syntaxTree', commands.syntaxTree.handle);
41 let editor = vscode.window.activeTextEditor 21 registerCommand('ra-lsp.extendSelection', commands.extendSelection.handle);
42 if (editor == null || editor.document.languageId != "rust") return 22 registerCommand('ra-lsp.matchingBrace', commands.matchingBrace.handle);
43 let request: ExtendSelectionParams = { 23 registerCommand('ra-lsp.joinLines', commands.joinLines.handle);
44 textDocument: { uri: editor.document.uri.toString() }, 24 registerCommand('ra-lsp.parentModule', commands.parentModule.handle);
45 selections: editor.selections.map((s) => { 25 registerCommand('ra-lsp.run', commands.runnables.handle);
46 return client.code2ProtocolConverter.asRange(s) 26 registerCommand('ra-lsp.applySourceChange', commands.applySourceChange.handle);
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 27
94 let doc = await vscode.workspace.openTextDocument(uri) 28 // Notifications are events triggered by the language server
95 let e = await vscode.window.showTextDocument(doc) 29 const allNotifications: Iterable<[string, lc.GenericNotificationHandler]> = [
96 e.selection = new vscode.Selection(range.start, range.start) 30 ['m/publishDecorations', notifications.publishDecorations.handle],
97 e.revealRange(range, vscode.TextEditorRevealType.InCenter) 31 ];
98 })
99 32
100 let prevRunnable: RunnableQuickPick | undefined = undefined 33 // The events below are plain old javascript events, triggered and handled by vscode
101 registerCommand('ra-lsp.run', async () => { 34 vscode.window.onDidChangeActiveTextEditor(events.changeActiveTextEditor.handle);
102 let editor = vscode.window.activeTextEditor
103 if (editor == null || editor.document.languageId != "rust") return
104 let textDocument: lc.TextDocumentIdentifier = {
105 uri: editor.document.uri.toString()
106 }
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 35
132 dispose(vscode.workspace.registerTextDocumentContentProvider( 36 const textDocumentContentProvider = new TextDocumentContentProvider();
37 disposeOnDeactivation(vscode.workspace.registerTextDocumentContentProvider(
133 'ra-lsp', 38 'ra-lsp',
134 textDocumentContentProvider 39 textDocumentContentProvider,
135 )) 40 ));
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 41
154// We need to order this after LS updates, but there's no API for that. 42 vscode.workspace.onDidChangeTextDocument(
155// Hence, good old setTimeout. 43 events.changeTextDocument.createHandler(textDocumentContentProvider),
156function afterLs(f: () => any) { 44 null,
157 setTimeout(f, 10) 45 context.subscriptions);
46
47 // Start the language server, finally!
48 Server.start(allNotifications);
158} 49}
159 50
160export function deactivate(): Thenable<void> { 51export function deactivate(): Thenable<void> {
161 if (!client) { 52 if (!Server.client) {
162 return Promise.resolve(); 53 return Promise.resolve();
163 } 54 }
164 return client.stop(); 55 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} 56}
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts
new file mode 100644
index 000000000..e2ac4d629
--- /dev/null
+++ b/editors/code/src/highlighting.ts
@@ -0,0 +1,82 @@
1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient';
3
4import { Server } from './server';
5
6export interface Decoration {
7 range: lc.Range;
8 tag: string;
9}
10
11export class Highlighter {
12 private static initDecorations(): Map<string, vscode.TextEditorDecorationType> {
13 const decor = (color: string) => vscode.window.createTextEditorDecorationType({ color });
14
15 const decorations: Iterable<[string, vscode.TextEditorDecorationType]> = [
16 ['background', decor('#3F3F3F')],
17 ['error', vscode.window.createTextEditorDecorationType({
18 borderColor: 'red',
19 borderStyle: 'none none dashed none',
20 })],
21 ['comment', decor('#7F9F7F')],
22 ['string', decor('#CC9393')],
23 ['keyword', decor('#F0DFAF')],
24 ['function', decor('#93E0E3')],
25 ['parameter', decor('#94BFF3')],
26 ['builtin', decor('#DD6718')],
27 ['text', decor('#DCDCCC')],
28 ['attribute', decor('#BFEBBF')],
29 ['literal', decor('#DFAF8F')],
30 ];
31
32 return new Map<string, vscode.TextEditorDecorationType>(decorations);
33 }
34
35 private decorations: (Map<string, vscode.TextEditorDecorationType> | null) = null;
36
37 public removeHighlights() {
38 if (this.decorations == null) {
39 return;
40 }
41
42 // Decorations are removed when the object is disposed
43 for (const decoration of this.decorations.values()) {
44 decoration.dispose();
45 }
46
47 this.decorations = null;
48 }
49
50 public setHighlights(
51 editor: vscode.TextEditor,
52 highlights: Decoration[],
53 ) {
54 // Initialize decorations if necessary
55 //
56 // Note: decoration objects need to be kept around so we can dispose them
57 // if the user disables syntax highlighting
58 if (this.decorations == null) {
59 this.decorations = Highlighter.initDecorations();
60 }
61
62 const byTag: Map<string, vscode.Range[]> = new Map();
63 for (const tag of this.decorations.keys()) {
64 byTag.set(tag, []);
65 }
66
67 for (const d of highlights) {
68 if (!byTag.get(d.tag)) {
69 continue;
70 }
71 byTag.get(d.tag)!.push(
72 Server.client.protocol2CodeConverter.asRange(d.range),
73 );
74 }
75
76 for (const tag of byTag.keys()) {
77 const dec = this.decorations.get(tag) as vscode.TextEditorDecorationType;
78 const ranges = byTag.get(tag)!;
79 editor.setDecorations(dec, ranges);
80 }
81 }
82}
diff --git a/editors/code/src/notifications/index.ts b/editors/code/src/notifications/index.ts
new file mode 100644
index 000000000..c56576865
--- /dev/null
+++ b/editors/code/src/notifications/index.ts
@@ -0,0 +1,5 @@
1import * as publishDecorations from './publish_decorations';
2
3export {
4 publishDecorations,
5};
diff --git a/editors/code/src/notifications/publish_decorations.ts b/editors/code/src/notifications/publish_decorations.ts
new file mode 100644
index 000000000..d8790386b
--- /dev/null
+++ b/editors/code/src/notifications/publish_decorations.ts
@@ -0,0 +1,20 @@
1import * as vscode from 'vscode';
2
3import { Decoration } from '../highlighting';
4import { Server } from '../server';
5
6export interface PublishDecorationsParams {
7 uri: string;
8 decorations: Decoration[];
9}
10
11export function handle(params: PublishDecorationsParams) {
12 const targetEditor = vscode.window.visibleTextEditors.find(
13 (editor) => editor.document.uri.toString() === params.uri,
14 );
15 if (!Server.config.highlightingOn || !targetEditor) { return; }
16 Server.highlighter.setHighlights(
17 targetEditor,
18 params.decorations,
19 );
20}
diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts
new file mode 100644
index 000000000..01fd80756
--- /dev/null
+++ b/editors/code/src/server.ts
@@ -0,0 +1,37 @@
1import * as lc from 'vscode-languageclient';
2
3import { Config } from './config';
4import { Highlighter } from './highlighting';
5
6export class Server {
7 public static highlighter = new Highlighter();
8 public static config = new Config();
9 public static client: lc.LanguageClient;
10
11 public static start(notificationHandlers: Iterable<[string, lc.GenericNotificationHandler]>) {
12 const run: lc.Executable = {
13 command: 'ra_lsp_server',
14 options: { cwd: '.' },
15 };
16 const serverOptions: lc.ServerOptions = {
17 run,
18 debug: run,
19 };
20 const clientOptions: lc.LanguageClientOptions = {
21 documentSelector: [{ scheme: 'file', language: 'rust' }],
22 };
23
24 Server.client = new lc.LanguageClient(
25 'ra-lsp',
26 'rust-analyzer languge server',
27 serverOptions,
28 clientOptions,
29 );
30 Server.client.onReady().then(() => {
31 for (const [type, handler] of notificationHandlers) {
32 Server.client.onNotification(type, handler);
33 }
34 });
35 Server.client.start();
36 }
37}
diff --git a/editors/code/tslint.json b/editors/code/tslint.json
new file mode 100644
index 000000000..ce48dfc95
--- /dev/null
+++ b/editors/code/tslint.json
@@ -0,0 +1,13 @@
1{
2 "defaultSeverity": "error",
3 "extends": [
4 "tslint:recommended"
5 ],
6 "jsRules": {},
7 "rules": {
8 "quotemark": [true, "single"],
9 "interface-name": false,
10 "object-literal-sort-keys": false
11 },
12 "rulesDirectory": []
13}