aboutsummaryrefslogtreecommitdiff
path: root/editors/code
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code')
-rw-r--r--editors/code/package.json34
-rw-r--r--editors/code/src/cargo.ts106
-rw-r--r--editors/code/src/client.ts31
-rw-r--r--editors/code/src/commands/runnables.ts70
-rw-r--r--editors/code/src/config.ts9
-rw-r--r--editors/code/src/ctx.ts2
6 files changed, 202 insertions, 50 deletions
diff --git a/editors/code/package.json b/editors/code/package.json
index d363e9fbb..a05a69752 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -27,6 +27,7 @@
27 "scripts": { 27 "scripts": {
28 "vscode:prepublish": "tsc && rollup -c", 28 "vscode:prepublish": "tsc && rollup -c",
29 "package": "vsce package -o rust-analyzer.vsix", 29 "package": "vsce package -o rust-analyzer.vsix",
30 "build": "tsc",
30 "watch": "tsc --watch", 31 "watch": "tsc --watch",
31 "lint": "tsfmt --verify && eslint -c .eslintrc.js --ext ts ./src", 32 "lint": "tsfmt --verify && eslint -c .eslintrc.js --ext ts ./src",
32 "fix": " tsfmt -r && eslint -c .eslintrc.js --ext ts ./src --fix" 33 "fix": " tsfmt -r && eslint -c .eslintrc.js --ext ts ./src --fix"
@@ -204,11 +205,6 @@
204 "default": [], 205 "default": [],
205 "description": "Paths to exclude from analysis." 206 "description": "Paths to exclude from analysis."
206 }, 207 },
207 "rust-analyzer.notifications.workspaceLoaded": {
208 "type": "boolean",
209 "default": true,
210 "markdownDescription": "Whether to show `workspace loaded` message."
211 },
212 "rust-analyzer.notifications.cargoTomlNotFound": { 208 "rust-analyzer.notifications.cargoTomlNotFound": {
213 "type": "boolean", 209 "type": "boolean",
214 "default": true, 210 "default": true,
@@ -293,7 +289,7 @@
293 "minItems": 1 289 "minItems": 1
294 }, 290 },
295 "default": null, 291 "default": null,
296 "markdownDescription": "Advanced option, fully override the command rust-analyzer uses for checking. The command should include `--message=format=json` or similar option." 292 "markdownDescription": "Advanced option, fully override the command rust-analyzer uses for checking. The command should include `--message-format=json` or similar option."
297 }, 293 },
298 "rust-analyzer.checkOnSave.allTargets": { 294 "rust-analyzer.checkOnSave.allTargets": {
299 "type": "boolean", 295 "type": "boolean",
@@ -396,6 +392,28 @@
396 "description": "Enable Proc macro support, cargo.loadOutDirsFromCheck must be enabled.", 392 "description": "Enable Proc macro support, cargo.loadOutDirsFromCheck must be enabled.",
397 "type": "boolean", 393 "type": "boolean",
398 "default": false 394 "default": false
395 },
396 "rust-analyzer.debug.engine": {
397 "type": "string",
398 "enum": [
399 "auto",
400 "vadimcn.vscode-lldb",
401 "ms-vscode.cpptools"
402 ],
403 "default": "auto",
404 "description": "Preffered debug engine.",
405 "markdownEnumDescriptions": [
406 "First try to use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), if it's not installed try to use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools).",
407 "Use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)",
408 "Use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)"
409 ]
410 },
411 "rust-analyzer.debug.sourceFileMap": {
412 "type": "object",
413 "description": "Optional source file mappings passed to the debug engine.",
414 "default": {
415 "/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"
416 }
399 } 417 }
400 } 418 }
401 }, 419 },
@@ -524,6 +542,10 @@
524 { 542 {
525 "id": "unresolvedReference", 543 "id": "unresolvedReference",
526 "description": "Style for names which can not be resolved due to compilation errors" 544 "description": "Style for names which can not be resolved due to compilation errors"
545 },
546 {
547 "id": "formatSpecifier",
548 "description": "Style for {} placeholders in format strings"
527 } 549 }
528 ], 550 ],
529 "semanticTokenModifiers": [ 551 "semanticTokenModifiers": [
diff --git a/editors/code/src/cargo.ts b/editors/code/src/cargo.ts
new file mode 100644
index 000000000..a328ba9bd
--- /dev/null
+++ b/editors/code/src/cargo.ts
@@ -0,0 +1,106 @@
1import * as cp from 'child_process';
2import * as readline from 'readline';
3import { OutputChannel } from 'vscode';
4
5interface CompilationArtifact {
6 fileName: string;
7 name: string;
8 kind: string;
9 isTest: boolean;
10}
11
12export class Cargo {
13 rootFolder: string;
14 env?: Record<string, string>;
15 output: OutputChannel;
16
17 public constructor(cargoTomlFolder: string, output: OutputChannel, env: Record<string, string> | undefined = undefined) {
18 this.rootFolder = cargoTomlFolder;
19 this.output = output;
20 this.env = env;
21 }
22
23 public async artifactsFromArgs(cargoArgs: string[]): Promise<CompilationArtifact[]> {
24 const artifacts: CompilationArtifact[] = [];
25
26 try {
27 await this.runCargo(cargoArgs,
28 message => {
29 if (message.reason === 'compiler-artifact' && message.executable) {
30 const isBinary = message.target.crate_types.includes('bin');
31 const isBuildScript = message.target.kind.includes('custom-build');
32 if ((isBinary && !isBuildScript) || message.profile.test) {
33 artifacts.push({
34 fileName: message.executable,
35 name: message.target.name,
36 kind: message.target.kind[0],
37 isTest: message.profile.test
38 });
39 }
40 }
41 else if (message.reason === 'compiler-message') {
42 this.output.append(message.message.rendered);
43 }
44 },
45 stderr => {
46 this.output.append(stderr);
47 }
48 );
49 }
50 catch (err) {
51 this.output.show(true);
52 throw new Error(`Cargo invocation has failed: ${err}`);
53 }
54
55 return artifacts;
56 }
57
58 public async executableFromArgs(args: string[]): Promise<string> {
59 const cargoArgs = [...args]; // to remain args unchanged
60 cargoArgs.push("--message-format=json");
61
62 const artifacts = await this.artifactsFromArgs(cargoArgs);
63
64 if (artifacts.length === 0) {
65 throw new Error('No compilation artifacts');
66 } else if (artifacts.length > 1) {
67 throw new Error('Multiple compilation artifacts are not supported.');
68 }
69
70 return artifacts[0].fileName;
71 }
72
73 runCargo(
74 cargoArgs: string[],
75 onStdoutJson: (obj: any) => void,
76 onStderrString: (data: string) => void
77 ): Promise<number> {
78 return new Promise<number>((resolve, reject) => {
79 const cargo = cp.spawn('cargo', cargoArgs, {
80 stdio: ['ignore', 'pipe', 'pipe'],
81 cwd: this.rootFolder,
82 env: this.env,
83 });
84
85 cargo.on('error', err => {
86 reject(new Error(`could not launch cargo: ${err}`));
87 });
88 cargo.stderr.on('data', chunk => {
89 onStderrString(chunk.toString());
90 });
91
92 const rl = readline.createInterface({ input: cargo.stdout });
93 rl.on('line', line => {
94 const message = JSON.parse(line);
95 onStdoutJson(message);
96 });
97
98 cargo.on('exit', (exitCode, _) => {
99 if (exitCode === 0)
100 resolve(exitCode);
101 else
102 reject(new Error(`exit code: ${exitCode}.`));
103 });
104 });
105 }
106} \ No newline at end of file
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 0ad4b63ae..cffdcf11a 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -4,7 +4,7 @@ import * as vscode from 'vscode';
4import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; 4import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed';
5import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; 5import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed';
6 6
7export async function createClient(serverPath: string, cwd: string): Promise<lc.LanguageClient> { 7export function createClient(serverPath: string, cwd: string): lc.LanguageClient {
8 // '.' Is the fallback if no folder is open 8 // '.' Is the fallback if no folder is open
9 // TODO?: Workspace folders support Uri's (eg: file://test.txt). 9 // TODO?: Workspace folders support Uri's (eg: file://test.txt).
10 // It might be a good idea to test if the uri points to a file. 10 // It might be a good idea to test if the uri points to a file.
@@ -42,35 +42,6 @@ export async function createClient(serverPath: string, cwd: string): Promise<lc.
42 clientOptions, 42 clientOptions,
43 ); 43 );
44 44
45 // HACK: This is an awful way of filtering out the decorations notifications
46 // However, pending proper support, this is the most effecitve approach
47 // Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages
48 // Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting)
49 // This also requires considering our settings strategy, which is work which needs doing
50 // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests
51 res._tracer = {
52 log: (messageOrDataObject: string | unknown, data?: string) => {
53 if (typeof messageOrDataObject === 'string') {
54 if (
55 messageOrDataObject.includes(
56 'rust-analyzer/publishDecorations',
57 ) ||
58 messageOrDataObject.includes(
59 'rust-analyzer/decorationsRequest',
60 )
61 ) {
62 // Don't log publish decorations requests
63 } else {
64 // @ts-ignore This is just a utility function
65 res.logTrace(messageOrDataObject, data);
66 }
67 } else {
68 // @ts-ignore
69 res.logObjectTrace(messageOrDataObject);
70 }
71 },
72 };
73
74 // To turn on all proposed features use: res.registerProposedFeatures(); 45 // To turn on all proposed features use: res.registerProposedFeatures();
75 // Here we want to enable CallHierarchyFeature and SemanticTokensFeature 46 // Here we want to enable CallHierarchyFeature and SemanticTokensFeature
76 // since they are available on stable. 47 // since they are available on stable.
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
index 2635a1440..d77e8188c 100644
--- a/editors/code/src/commands/runnables.ts
+++ b/editors/code/src/commands/runnables.ts
@@ -1,8 +1,10 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import * as ra from '../rust-analyzer-api'; 3import * as ra from '../rust-analyzer-api';
4import * as os from "os";
4 5
5import { Ctx, Cmd } from '../ctx'; 6import { Ctx, Cmd } from '../ctx';
7import { Cargo } from '../cargo';
6 8
7export function run(ctx: Ctx): Cmd { 9export function run(ctx: Ctx): Cmd {
8 let prevRunnable: RunnableQuickPick | undefined; 10 let prevRunnable: RunnableQuickPick | undefined;
@@ -62,25 +64,69 @@ export function runSingle(ctx: Ctx): Cmd {
62 }; 64 };
63} 65}
64 66
67function getLldbDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): vscode.DebugConfiguration {
68 return {
69 type: "lldb",
70 request: "launch",
71 name: config.label,
72 cargo: {
73 args: config.args,
74 },
75 args: config.extraArgs,
76 cwd: config.cwd,
77 sourceMap: sourceFileMap
78 };
79}
80
81const debugOutput = vscode.window.createOutputChannel("Debug");
82
83async function getCppvsDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): Promise<vscode.DebugConfiguration> {
84 debugOutput.clear();
85
86 const cargo = new Cargo(config.cwd || '.', debugOutput);
87 const executable = await cargo.executableFromArgs(config.args);
88
89 // if we are here, there were no compilation errors.
90 return {
91 type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg',
92 request: "launch",
93 name: config.label,
94 program: executable,
95 args: config.extraArgs,
96 cwd: config.cwd,
97 sourceFileMap: sourceFileMap,
98 };
99}
100
65export function debugSingle(ctx: Ctx): Cmd { 101export function debugSingle(ctx: Ctx): Cmd {
66 return async (config: ra.Runnable) => { 102 return async (config: ra.Runnable) => {
67 const editor = ctx.activeRustEditor; 103 const editor = ctx.activeRustEditor;
68 if (!editor) return; 104 if (!editor) return;
69 if (!vscode.extensions.getExtension("vadimcn.vscode-lldb")) { 105
70 vscode.window.showErrorMessage("Install `vadimcn.vscode-lldb` extension for debugging"); 106 const lldbId = "vadimcn.vscode-lldb";
107 const cpptoolsId = "ms-vscode.cpptools";
108
109 const debugEngineId = ctx.config.debug.engine;
110 let debugEngine = null;
111 if (debugEngineId === "auto") {
112 debugEngine = vscode.extensions.getExtension(lldbId);
113 if (!debugEngine) {
114 debugEngine = vscode.extensions.getExtension(cpptoolsId);
115 }
116 }
117 else {
118 debugEngine = vscode.extensions.getExtension(debugEngineId);
119 }
120
121 if (!debugEngine) {
122 vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=${lldbId})`
123 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=${cpptoolsId}) extension for debugging.`);
71 return; 124 return;
72 } 125 }
73 126
74 const debugConfig = { 127 const debugConfig = lldbId === debugEngine.id
75 type: "lldb", 128 ? getLldbDebugConfig(config, ctx.config.debug.sourceFileMap)
76 request: "launch", 129 : await getCppvsDebugConfig(config, ctx.config.debug.sourceFileMap);
77 name: config.label,
78 cargo: {
79 args: config.args,
80 },
81 args: config.extraArgs,
82 cwd: config.cwd
83 };
84 130
85 return vscode.debug.startDebugging(undefined, debugConfig); 131 return vscode.debug.startDebugging(undefined, debugConfig);
86 }; 132 };
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 3b2eec8ba..110e54180 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -92,7 +92,6 @@ export class Config {
92 get askBeforeDownload() { return this.get<boolean>("updates.askBeforeDownload"); } 92 get askBeforeDownload() { return this.get<boolean>("updates.askBeforeDownload"); }
93 get traceExtension() { return this.get<boolean>("trace.extension"); } 93 get traceExtension() { return this.get<boolean>("trace.extension"); }
94 94
95
96 get inlayHints() { 95 get inlayHints() {
97 return { 96 return {
98 typeHints: this.get<boolean>("inlayHints.typeHints"), 97 typeHints: this.get<boolean>("inlayHints.typeHints"),
@@ -107,4 +106,12 @@ export class Config {
107 command: this.get<string>("checkOnSave.command"), 106 command: this.get<string>("checkOnSave.command"),
108 }; 107 };
109 } 108 }
109
110 get debug() {
111 return {
112 engine: this.get<string>("debug.engine"),
113 sourceFileMap: this.get<Record<string, string>>("debug.sourceFileMap"),
114 };
115 }
116
110} 117}
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index f7ed62d03..41df11991 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -21,7 +21,7 @@ export class Ctx {
21 serverPath: string, 21 serverPath: string,
22 cwd: string, 22 cwd: string,
23 ): Promise<Ctx> { 23 ): Promise<Ctx> {
24 const client = await createClient(serverPath, cwd); 24 const client = createClient(serverPath, cwd);
25 const res = new Ctx(config, extCtx, client, serverPath); 25 const res = new Ctx(config, extCtx, client, serverPath);
26 res.pushCleanup(client.start()); 26 res.pushCleanup(client.start());
27 await client.onReady(); 27 await client.onReady();