aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src
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
parente4fdfd15012c983e4555996aa466b57d787e4385 (diff)
Refactor vscode extension
Diffstat (limited to 'editors/code/src')
-rw-r--r--editors/code/src/commands.ts17
-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/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/events.ts7
-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/extension.ts441
-rw-r--r--editors/code/src/highlighting.ts78
-rw-r--r--editors/code/src/server.ts74
14 files changed, 518 insertions, 415 deletions
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
new file mode 100644
index 000000000..99cac3379
--- /dev/null
+++ b/editors/code/src/commands.ts
@@ -0,0 +1,17 @@
1import * as applySourceChange from './commands/apply_source_change';
2import * as extendSelection from './commands/extend_selection';
3import * as joinLines from './commands/join_lines';
4import * as matchingBrace from './commands/matching_brace';
5import * as parentModule from './commands/parent_module';
6import * as runnables from './commands/runnables';
7import * as syntaxTree from './commands/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/apply_source_change.ts b/editors/code/src/commands/apply_source_change.ts
new file mode 100644
index 000000000..dcbbb2b09
--- /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 console.log(`applySOurceChange ${JSON.stringify(change)}`)
22 let wsEdit = new vscode.WorkspaceEdit()
23 for (let sourceEdit of change.sourceFileEdits) {
24 let uri = Server.client.protocol2CodeConverter.asUri(sourceEdit.textDocument.uri)
25 let edits = Server.client.protocol2CodeConverter.asTextEdits(sourceEdit.edits)
26 wsEdit.set(uri, edits)
27 }
28 let created;
29 let moved;
30 for (let fsEdit of change.fileSystemEdits) {
31 if (fsEdit.type == "createFile") {
32 let uri = vscode.Uri.parse(fsEdit.uri!)
33 wsEdit.createFile(uri)
34 created = uri
35 } else if (fsEdit.type == "moveFile") {
36 let src = vscode.Uri.parse(fsEdit.src!)
37 let dst = vscode.Uri.parse(fsEdit.dst!)
38 wsEdit.renameFile(src, dst)
39 moved = dst
40 } else {
41 console.error(`unknown op: ${JSON.stringify(fsEdit)}`)
42 }
43 }
44 let toOpen = created || moved
45 let toReveal = change.cursorPosition
46 await vscode.workspace.applyEdit(wsEdit)
47 if (toOpen) {
48 let doc = await vscode.workspace.openTextDocument(toOpen)
49 await vscode.window.showTextDocument(doc)
50 } else if (toReveal) {
51 let uri = Server.client.protocol2CodeConverter.asUri(toReveal.textDocument.uri)
52 let position = Server.client.protocol2CodeConverter.asPosition(toReveal.position)
53 let 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..b90828ba9
--- /dev/null
+++ b/editors/code/src/commands/extend_selection.ts
@@ -0,0 +1,29 @@
1import * as vscode from 'vscode';
2
3import { TextDocumentIdentifier, Range } 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 let editor = vscode.window.activeTextEditor
17 if (editor == null || editor.document.languageId != "rust") return
18 let request: ExtendSelectionParams = {
19 textDocument: { uri: editor.document.uri.toString() },
20 selections: editor.selections.map((s) => {
21 return Server.client.code2ProtocolConverter.asRange(s)
22 })
23 }
24 let response = await Server.client.sendRequest<ExtendSelectionResult>("m/extendSelection", request)
25 editor.selections = response.selections.map((range: Range) => {
26 let r = Server.client.protocol2CodeConverter.asRange(range)
27 return new vscode.Selection(r.start, r.end)
28 })
29}
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts
new file mode 100644
index 000000000..7ae7b9d76
--- /dev/null
+++ b/editors/code/src/commands/join_lines.ts
@@ -0,0 +1,21 @@
1import * as vscode from 'vscode';
2
3import { TextDocumentIdentifier, Range } 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 let editor = vscode.window.activeTextEditor
14 if (editor == null || editor.document.languageId != "rust") return
15 let request: JoinLinesParams = {
16 textDocument: { uri: editor.document.uri.toString() },
17 range: Server.client.code2ProtocolConverter.asRange(editor.selection),
18 }
19 let 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..572c15ce8
--- /dev/null
+++ b/editors/code/src/commands/matching_brace.ts
@@ -0,0 +1,27 @@
1import * as vscode from 'vscode';
2
3import { TextDocumentIdentifier, Position } from "vscode-languageclient";
4import { Server } from '../server';
5
6interface FindMatchingBraceParams {
7 textDocument: TextDocumentIdentifier;
8 offsets: Position[];
9}
10
11export async function handle() {
12 let editor = vscode.window.activeTextEditor
13 if (editor == null || editor.document.languageId != "rust") return
14 let 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 let response = await Server.client.sendRequest<Position[]>("m/findMatchingBrace", request)
21 editor.selections = editor.selections.map((sel, idx) => {
22 let active = Server.client.protocol2CodeConverter.asPosition(response[idx])
23 let 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..dae60bfb4
--- /dev/null
+++ b/editors/code/src/commands/parent_module.ts
@@ -0,0 +1,22 @@
1import * as vscode from 'vscode';
2
3import { TextDocumentIdentifier, Location } from "vscode-languageclient";
4import { Server } from '../server';
5
6export async function handle() {
7 let editor = vscode.window.activeTextEditor
8 if (editor == null || editor.document.languageId != "rust") return
9 let request: TextDocumentIdentifier = {
10 uri: editor.document.uri.toString()
11 }
12 let response = await Server.client.sendRequest<Location[]>("m/parentModule", request)
13 let loc = response[0]
14 if (loc == null) return
15 let uri = Server.client.protocol2CodeConverter.asUri(loc.uri)
16 let range = Server.client.protocol2CodeConverter.asRange(loc.range)
17
18 let doc = await vscode.workspace.openTextDocument(uri)
19 let 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..45c16497d
--- /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 label: string;
20 description?: string | undefined;
21 detail?: string | undefined;
22 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: Array<string>;
34 env?: { [key: string]: string };
35}
36
37function createTask(spec: Runnable): vscode.Task {
38 const TASK_SOURCE = 'Rust';
39 let definition: CargoTaskDefinition = {
40 type: 'cargo',
41 label: 'cargo',
42 command: spec.bin,
43 args: spec.args,
44 env: spec.env
45 }
46
47 let execCmd = `${definition.command} ${definition.args.join(' ')}`;
48 let execOption: vscode.ShellExecutionOptions = {
49 cwd: '.',
50 env: definition.env,
51 };
52 let exec = new vscode.ShellExecution(`clear; ${execCmd}`, execOption);
53
54 let f = vscode.workspace.workspaceFolders![0]
55 let t = new vscode.Task(definition, f, definition.label, TASK_SOURCE, exec, ['$rustc']);
56 return t;
57}
58
59let prevRunnable: RunnableQuickPick | undefined = undefined
60export async function handle() {
61 let editor = vscode.window.activeTextEditor
62 if (editor == null || editor.document.languageId != "rust") return
63 let textDocument: lc.TextDocumentIdentifier = {
64 uri: editor.document.uri.toString()
65 }
66 let params: RunnablesParams = {
67 textDocument,
68 position: Server.client.code2ProtocolConverter.asPosition(editor.selection.active)
69 }
70 let runnables = await Server.client.sendRequest<Runnable[]>('m/runnables', params)
71 let items: RunnableQuickPick[] = []
72 if (prevRunnable) {
73 items.push(prevRunnable)
74 }
75 for (let r of runnables) {
76 if (prevRunnable && JSON.stringify(prevRunnable.runnable) == JSON.stringify(r)) {
77 continue
78 }
79 items.push(new RunnableQuickPick(r))
80 }
81 let item = await vscode.window.showQuickPick(items)
82 if (item) {
83 item.detail = "rerun"
84 prevRunnable = item
85 let 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..d5daa9302
--- /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 let editor = vscode.window.activeTextEditor;
14 if (editor == null) return ""
15 let 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 let document = await vscode.workspace.openTextDocument(syntaxTreeUri)
37 return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true)
38}
diff --git a/editors/code/src/events.ts b/editors/code/src/events.ts
new file mode 100644
index 000000000..b143bb256
--- /dev/null
+++ b/editors/code/src/events.ts
@@ -0,0 +1,7 @@
1import * as changeActiveTextEditor from './events/change_active_text_editor'
2import * as changeTextDocument from './events/change_text_document';
3
4export {
5 changeActiveTextEditor,
6 changeTextDocument
7} \ No newline at end of file
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..bbdd53098
--- /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 { Server } from "../server";
5import { Decoration } from "../highlighting";
6
7export async function handle(editor: TextEditor | undefined) {
8 if (!Server.config.highlightingOn || !editor || editor.document.languageId != 'rust') return
9 let params: TextDocumentIdentifier = {
10 uri: editor.document.uri.toString()
11 }
12 let decorations = await Server.client.sendRequest<Decoration[]>("m/decorationsRequest", params)
13 Server.highlighter.setHighlights(editor, decorations)
14} \ No newline at end of file
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..83ee6c9ee
--- /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 let 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/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}
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts
new file mode 100644
index 000000000..169ddb0df
--- /dev/null
+++ b/editors/code/src/highlighting.ts
@@ -0,0 +1,78 @@
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 decorations: { [index: string]: vscode.TextEditorDecorationType };
13 constructor() {
14 this.decorations = {};
15 }
16
17 removeHighlights() {
18 for (let tag in this.decorations) {
19 this.decorations[tag].dispose();
20 }
21
22 this.decorations = {};
23 }
24
25 setHighlights(
26 editor: vscode.TextEditor,
27 highlights: Array<Decoration>
28 ) {
29 // Initialize decorations if necessary
30 //
31 // Note: decoration objects need to be kept around so we can dispose them
32 // if the user disables syntax highlighting
33 if (Object.keys(this.decorations).length === 0) {
34 this.initDecorations();
35 }
36
37 let byTag: Map<string, vscode.Range[]> = new Map()
38 for (let tag in this.decorations) {
39 byTag.set(tag, [])
40 }
41
42 for (let d of highlights) {
43 if (!byTag.get(d.tag)) {
44 console.log(`unknown tag ${d.tag}`)
45 continue
46 }
47 byTag.get(d.tag)!.push(
48 Server.client.protocol2CodeConverter.asRange(d.range)
49 )
50 }
51
52 for (let tag of byTag.keys()) {
53 let dec: vscode.TextEditorDecorationType = this.decorations[tag]
54 let ranges = byTag.get(tag)!
55 editor.setDecorations(dec, ranges)
56 }
57 }
58
59 private initDecorations() {
60 const decor = (obj: any) => vscode.window.createTextEditorDecorationType({ color: obj })
61 this.decorations = {
62 background: decor("#3F3F3F"),
63 error: vscode.window.createTextEditorDecorationType({
64 borderColor: "red",
65 borderStyle: "none none dashed none",
66 }),
67 comment: decor("#7F9F7F"),
68 string: decor("#CC9393"),
69 keyword: decor("#F0DFAF"),
70 function: decor("#93E0E3"),
71 parameter: decor("#94BFF3"),
72 builtin: decor("#DD6718"),
73 text: decor("#DCDCCC"),
74 attribute: decor("#BFEBBF"),
75 literal: decor("#DFAF8F"),
76 }
77 }
78}
diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts
new file mode 100644
index 000000000..c1c95e008
--- /dev/null
+++ b/editors/code/src/server.ts
@@ -0,0 +1,74 @@
1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'
3
4import { Highlighter, Decoration } from './highlighting';
5
6export class Config {
7 highlightingOn = true;
8
9 constructor() {
10 vscode.workspace.onDidChangeConfiguration(_ => this.userConfigChanged());
11 this.userConfigChanged();
12 }
13
14 userConfigChanged() {
15 let config = vscode.workspace.getConfiguration('ra-lsp');
16 if (config.has('highlightingOn')) {
17 this.highlightingOn = config.get('highlightingOn') as boolean;
18 };
19
20 if (!this.highlightingOn) {
21 Server.highlighter.removeHighlights();
22 }
23 }
24}
25
26export class Server {
27 static highlighter = new Highlighter();
28 static config = new Config();
29 static client: lc.LanguageClient;
30
31
32 static start() {
33 let run: lc.Executable = {
34 command: "ra_lsp_server",
35 options: { cwd: "." }
36 }
37 let serverOptions: lc.ServerOptions = {
38 run,
39 debug: run
40 };
41
42 let clientOptions: lc.LanguageClientOptions = {
43 documentSelector: [{ scheme: 'file', language: 'rust' }],
44 };
45
46 Server.client = new lc.LanguageClient(
47 'ra-lsp',
48 'rust-analyzer languge server',
49 serverOptions,
50 clientOptions,
51 );
52 Server.client.onReady().then(() => {
53 Server.client.onNotification(
54 "m/publishDecorations",
55 (params: PublishDecorationsParams) => {
56 let editor = vscode.window.visibleTextEditors.find(
57 (editor) => editor.document.uri.toString() == params.uri
58 )
59 if (!Server.config.highlightingOn || !editor) return;
60 Server.highlighter.setHighlights(
61 editor,
62 params.decorations,
63 )
64 }
65 )
66 })
67 Server.client.start();
68 }
69}
70
71interface PublishDecorationsParams {
72 uri: string,
73 decorations: Decoration[],
74}