aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-07-31 03:12:44 +0100
committerZac Pullar-Strecker <[email protected]>2020-07-31 03:12:44 +0100
commitf05d7b41a719d848844b054a16477b29d0f063c6 (patch)
tree0a8a0946e8aef2ce64d4c13d0035ba41cce2daf3 /editors/code/src
parent73ff610e41959e3e7c78a2b4b25b086883132956 (diff)
parent6b7cb8b5ab539fc4333ce34bc29bf77c976f232a (diff)
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Hasn't fixed tests yet.
Diffstat (limited to 'editors/code/src')
-rw-r--r--editors/code/src/client.ts8
-rw-r--r--editors/code/src/commands.ts51
-rw-r--r--editors/code/src/config.ts14
-rw-r--r--editors/code/src/ctx.ts43
-rw-r--r--editors/code/src/debug.ts16
-rw-r--r--editors/code/src/lsp_ext.ts10
-rw-r--r--editors/code/src/main.ts54
-rw-r--r--editors/code/src/net.ts21
-rw-r--r--editors/code/src/persistent_state.ts2
-rw-r--r--editors/code/src/run.ts31
-rw-r--r--editors/code/src/snippets.ts4
-rw-r--r--editors/code/src/util.ts45
12 files changed, 238 insertions, 61 deletions
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 65ad573d8..18948cb3c 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -41,6 +41,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
41 const clientOptions: lc.LanguageClientOptions = { 41 const clientOptions: lc.LanguageClientOptions = {
42 documentSelector: [{ scheme: 'file', language: 'rust' }], 42 documentSelector: [{ scheme: 'file', language: 'rust' }],
43 initializationOptions: vscode.workspace.getConfiguration("rust-analyzer"), 43 initializationOptions: vscode.workspace.getConfiguration("rust-analyzer"),
44 diagnosticCollectionName: "rustc",
44 traceOutputChannel, 45 traceOutputChannel,
45 middleware: { 46 middleware: {
46 // Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576 47 // Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576
@@ -66,7 +67,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
66 return Promise.resolve(null); 67 return Promise.resolve(null);
67 }); 68 });
68 }, 69 },
69 // Using custom handling of CodeActions where each code action is resloved lazily 70 // Using custom handling of CodeActions where each code action is resolved lazily
70 // That's why we are not waiting for any command or edits 71 // That's why we are not waiting for any command or edits
71 async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { 72 async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
72 const params: lc.CodeActionParams = { 73 const params: lc.CodeActionParams = {
@@ -87,7 +88,8 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
87 continue; 88 continue;
88 } 89 }
89 assert(isCodeActionWithoutEditsAndCommands(item), "We don't expect edits or commands here"); 90 assert(isCodeActionWithoutEditsAndCommands(item), "We don't expect edits or commands here");
90 const action = new vscode.CodeAction(item.title); 91 const kind = client.protocol2CodeConverter.asCodeActionKind((item as any).kind);
92 const action = new vscode.CodeAction(item.title, kind);
91 const group = (item as any).group; 93 const group = (item as any).group;
92 const id = (item as any).id; 94 const id = (item as any).id;
93 const resolveParams: ra.ResolveCodeActionParams = { 95 const resolveParams: ra.ResolveCodeActionParams = {
@@ -116,6 +118,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
116 result[index] = items[0]; 118 result[index] = items[0];
117 } else { 119 } else {
118 const action = new vscode.CodeAction(group); 120 const action = new vscode.CodeAction(group);
121 action.kind = items[0].kind;
119 action.command = { 122 action.command = {
120 command: "rust-analyzer.applyActionGroup", 123 command: "rust-analyzer.applyActionGroup",
121 title: "", 124 title: "",
@@ -161,6 +164,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
161 caps.codeActionGroup = true; 164 caps.codeActionGroup = true;
162 caps.resolveCodeAction = true; 165 caps.resolveCodeAction = true;
163 caps.hoverActions = true; 166 caps.hoverActions = true;
167 caps.statusNotification = true;
164 capabilities.experimental = caps; 168 capabilities.experimental = caps;
165 } 169 }
166 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { 170 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 0e78f5101..d0faf4745 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -55,6 +55,38 @@ export function analyzerStatus(ctx: Ctx): Cmd {
55 }; 55 };
56} 56}
57 57
58export function memoryUsage(ctx: Ctx): Cmd {
59 const tdcp = new class implements vscode.TextDocumentContentProvider {
60 readonly uri = vscode.Uri.parse('rust-analyzer-memory://memory');
61 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
62
63 provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
64 if (!vscode.window.activeTextEditor) return '';
65
66 return ctx.client.sendRequest(ra.memoryUsage, null).then((mem) => {
67 return 'Per-query memory usage:\n' + mem + '\n(note: database has been cleared)';
68 });
69 }
70
71 get onDidChange(): vscode.Event<vscode.Uri> {
72 return this.eventEmitter.event;
73 }
74 }();
75
76 ctx.pushCleanup(
77 vscode.workspace.registerTextDocumentContentProvider(
78 'rust-analyzer-memory',
79 tdcp,
80 ),
81 );
82
83 return async () => {
84 tdcp.eventEmitter.fire(tdcp.uri);
85 const document = await vscode.workspace.openTextDocument(tdcp.uri);
86 return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
87 };
88}
89
58export function matchingBrace(ctx: Ctx): Cmd { 90export function matchingBrace(ctx: Ctx): Cmd {
59 return async () => { 91 return async () => {
60 const editor = ctx.activeRustEditor; 92 const editor = ctx.activeRustEditor;
@@ -153,15 +185,22 @@ export function parentModule(ctx: Ctx): Cmd {
153 185
154export function ssr(ctx: Ctx): Cmd { 186export function ssr(ctx: Ctx): Cmd {
155 return async () => { 187 return async () => {
188 const editor = vscode.window.activeTextEditor;
156 const client = ctx.client; 189 const client = ctx.client;
157 if (!client) return; 190 if (!editor || !client) return;
191
192 const position = editor.selection.active;
193 const selections = editor.selections;
194 const textDocument = { uri: editor.document.uri.toString() };
158 195
159 const options: vscode.InputBoxOptions = { 196 const options: vscode.InputBoxOptions = {
160 value: "() ==>> ()", 197 value: "() ==>> ()",
161 prompt: "Enter request, for example 'Foo($a) ==> Foo::new($a)' ", 198 prompt: "Enter request, for example 'Foo($a) ==> Foo::new($a)' ",
162 validateInput: async (x: string) => { 199 validateInput: async (x: string) => {
163 try { 200 try {
164 await client.sendRequest(ra.ssr, { query: x, parseOnly: true }); 201 await client.sendRequest(ra.ssr, {
202 query: x, parseOnly: true, textDocument, position, selections,
203 });
165 } catch (e) { 204 } catch (e) {
166 return e.toString(); 205 return e.toString();
167 } 206 }
@@ -176,7 +215,9 @@ export function ssr(ctx: Ctx): Cmd {
176 title: "Structured search replace in progress...", 215 title: "Structured search replace in progress...",
177 cancellable: false, 216 cancellable: false,
178 }, async (_progress, _token) => { 217 }, async (_progress, _token) => {
179 const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false }); 218 const edit = await client.sendRequest(ra.ssr, {
219 query: request, parseOnly: false, textDocument, position, selections,
220 });
180 221
181 await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit)); 222 await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
182 }); 223 });
@@ -330,8 +371,8 @@ export function expandMacro(ctx: Ctx): Cmd {
330 }; 371 };
331} 372}
332 373
333export function collectGarbage(ctx: Ctx): Cmd { 374export function reloadWorkspace(ctx: Ctx): Cmd {
334 return async () => ctx.client.sendRequest(ra.collectGarbage, null); 375 return async () => ctx.client.sendRequest(ra.reloadWorkspace, null);
335} 376}
336 377
337export function showReferences(ctx: Ctx): Cmd { 378export function showReferences(ctx: Ctx): Cmd {
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index fc95a7de6..033b04b60 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -5,6 +5,8 @@ export type UpdatesChannel = "stable" | "nightly";
5 5
6export const NIGHTLY_TAG = "nightly"; 6export const NIGHTLY_TAG = "nightly";
7 7
8export type RunnableEnvCfg = undefined | Record<string, string> | { mask?: string; env: Record<string, string> }[];
9
8export class Config { 10export class Config {
9 readonly extensionId = "matklad.rust-analyzer"; 11 readonly extensionId = "matklad.rust-analyzer";
10 12
@@ -37,10 +39,10 @@ export class Config {
37 39
38 private refreshLogging() { 40 private refreshLogging() {
39 log.setEnabled(this.traceExtension); 41 log.setEnabled(this.traceExtension);
40 log.debug( 42 log.info("Extension version:", this.package.version);
41 "Extension version:", this.package.version, 43
42 "using configuration:", this.cfg 44 const cfg = Object.entries(this.cfg).filter(([_, val]) => !(val instanceof Function));
43 ); 45 log.info("Using configuration", Object.fromEntries(cfg));
44 } 46 }
45 47
46 private async onDidChangeConfiguration(event: vscode.ConfigurationChangeEvent) { 48 private async onDidChangeConfiguration(event: vscode.ConfigurationChangeEvent) {
@@ -114,6 +116,10 @@ export class Config {
114 return this.get<string | undefined>("cargoRunner"); 116 return this.get<string | undefined>("cargoRunner");
115 } 117 }
116 118
119 get runnableEnv() {
120 return this.get<RunnableEnvCfg>("runnableEnv");
121 }
122
117 get debug() { 123 get debug() {
118 // "/rustc/<id>" used by suggestions only. 124 // "/rustc/<id>" used by suggestions only.
119 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap"); 125 const { ["/rustc/<id>"]: _, ...sourceFileMap } = this.get<Record<string, string>>("debug.sourceFileMap");
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 41df11991..6e767babf 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -1,9 +1,11 @@
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 './lsp_ext';
3 4
4import { Config } from './config'; 5import { Config } from './config';
5import { createClient } from './client'; 6import { createClient } from './client';
6import { isRustEditor, RustEditor } from './util'; 7import { isRustEditor, RustEditor } from './util';
8import { Status } from './lsp_ext';
7 9
8export class Ctx { 10export class Ctx {
9 private constructor( 11 private constructor(
@@ -11,6 +13,7 @@ export class Ctx {
11 private readonly extCtx: vscode.ExtensionContext, 13 private readonly extCtx: vscode.ExtensionContext,
12 readonly client: lc.LanguageClient, 14 readonly client: lc.LanguageClient,
13 readonly serverPath: string, 15 readonly serverPath: string,
16 readonly statusBar: vscode.StatusBarItem,
14 ) { 17 ) {
15 18
16 } 19 }
@@ -22,9 +25,18 @@ export class Ctx {
22 cwd: string, 25 cwd: string,
23 ): Promise<Ctx> { 26 ): Promise<Ctx> {
24 const client = createClient(serverPath, cwd); 27 const client = createClient(serverPath, cwd);
25 const res = new Ctx(config, extCtx, client, serverPath); 28
29 const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
30 extCtx.subscriptions.push(statusBar);
31 statusBar.text = "rust-analyzer";
32 statusBar.tooltip = "ready";
33 statusBar.show();
34
35 const res = new Ctx(config, extCtx, client, serverPath, statusBar);
36
26 res.pushCleanup(client.start()); 37 res.pushCleanup(client.start());
27 await client.onReady(); 38 await client.onReady();
39 client.onNotification(ra.status, (status) => res.setStatus(status));
28 return res; 40 return res;
29 } 41 }
30 42
@@ -54,6 +66,35 @@ export class Ctx {
54 return this.extCtx.subscriptions; 66 return this.extCtx.subscriptions;
55 } 67 }
56 68
69 setStatus(status: Status) {
70 switch (status) {
71 case "loading":
72 this.statusBar.text = "$(sync~spin) rust-analyzer";
73 this.statusBar.tooltip = "Loading the project";
74 this.statusBar.command = undefined;
75 this.statusBar.color = undefined;
76 break;
77 case "ready":
78 this.statusBar.text = "rust-analyzer";
79 this.statusBar.tooltip = "Ready";
80 this.statusBar.command = undefined;
81 this.statusBar.color = undefined;
82 break;
83 case "invalid":
84 this.statusBar.text = "$(error) rust-analyzer";
85 this.statusBar.tooltip = "Failed to load the project";
86 this.statusBar.command = undefined;
87 this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground");
88 break;
89 case "needsReload":
90 this.statusBar.text = "$(warning) rust-analyzer";
91 this.statusBar.tooltip = "Click to reload";
92 this.statusBar.command = "rust-analyzer.reloadWorkspace";
93 this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground");
94 break;
95 }
96 }
97
57 pushCleanup(d: Disposable) { 98 pushCleanup(d: Disposable) {
58 this.extCtx.subscriptions.push(d); 99 this.extCtx.subscriptions.push(d);
59 } 100 }
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index 61c12dbe0..bd92c5b6d 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -5,9 +5,10 @@ import * as ra from './lsp_ext';
5 5
6import { Cargo } from './toolchain'; 6import { Cargo } from './toolchain';
7import { Ctx } from "./ctx"; 7import { Ctx } from "./ctx";
8import { prepareEnv } from "./run";
8 9
9const debugOutput = vscode.window.createOutputChannel("Debug"); 10const debugOutput = vscode.window.createOutputChannel("Debug");
10type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration; 11type DebugConfigProvider = (config: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>) => vscode.DebugConfiguration;
11 12
12export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> { 13export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
13 const scope = ctx.activeRustEditor?.document.uri; 14 const scope = ctx.activeRustEditor?.document.uri;
@@ -92,7 +93,8 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
92 } 93 }
93 94
94 const executable = await getDebugExecutable(runnable); 95 const executable = await getDebugExecutable(runnable);
95 const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), debugOptions.sourceFileMap); 96 const env = prepareEnv(runnable, ctx.config.runnableEnv);
97 const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), env, debugOptions.sourceFileMap);
96 if (debugConfig.type in debugOptions.engineSettings) { 98 if (debugConfig.type in debugOptions.engineSettings) {
97 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; 99 const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
98 for (var key in settingsMap) { 100 for (var key in settingsMap) {
@@ -121,7 +123,7 @@ async function getDebugExecutable(runnable: ra.Runnable): Promise<string> {
121 return executable; 123 return executable;
122} 124}
123 125
124function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { 126function getLldbDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
125 return { 127 return {
126 type: "lldb", 128 type: "lldb",
127 request: "launch", 129 request: "launch",
@@ -130,11 +132,12 @@ function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFil
130 args: runnable.args.executableArgs, 132 args: runnable.args.executableArgs,
131 cwd: runnable.args.workspaceRoot, 133 cwd: runnable.args.workspaceRoot,
132 sourceMap: sourceFileMap, 134 sourceMap: sourceFileMap,
133 sourceLanguages: ["rust"] 135 sourceLanguages: ["rust"],
136 env
134 }; 137 };
135} 138}
136 139
137function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration { 140function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, env: Record<string, string>, sourceFileMap?: Record<string, string>): vscode.DebugConfiguration {
138 return { 141 return {
139 type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg", 142 type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg",
140 request: "launch", 143 request: "launch",
@@ -142,6 +145,7 @@ function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFi
142 program: executable, 145 program: executable,
143 args: runnable.args.executableArgs, 146 args: runnable.args.executableArgs,
144 cwd: runnable.args.workspaceRoot, 147 cwd: runnable.args.workspaceRoot,
145 sourceFileMap: sourceFileMap, 148 sourceFileMap,
149 env,
146 }; 150 };
147} 151}
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index e16ea799c..494d51c83 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -5,8 +5,12 @@
5import * as lc from "vscode-languageclient"; 5import * as lc from "vscode-languageclient";
6 6
7export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus"); 7export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus");
8export const memoryUsage = new lc.RequestType<null, string, void>("rust-analyzer/memoryUsage");
8 9
9export const collectGarbage = new lc.RequestType<null, null, void>("rust-analyzer/collectGarbage"); 10export type Status = "loading" | "ready" | "invalid" | "needsReload";
11export const status = new lc.NotificationType<Status>("rust-analyzer/status");
12
13export const reloadWorkspace = new lc.RequestType<null, null, void>("rust-analyzer/reloadWorkspace");
10 14
11export interface SyntaxTreeParams { 15export interface SyntaxTreeParams {
12 textDocument: lc.TextDocumentIdentifier; 16 textDocument: lc.TextDocumentIdentifier;
@@ -60,6 +64,7 @@ export interface Runnable {
60 workspaceRoot?: string; 64 workspaceRoot?: string;
61 cargoArgs: string[]; 65 cargoArgs: string[];
62 executableArgs: string[]; 66 executableArgs: string[];
67 expectTest?: boolean;
63 }; 68 };
64} 69}
65export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>("experimental/runnables"); 70export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>("experimental/runnables");
@@ -88,6 +93,9 @@ export const inlayHints = new lc.RequestType<InlayHintsParams, InlayHint[], void
88export interface SsrParams { 93export interface SsrParams {
89 query: string; 94 query: string;
90 parseOnly: boolean; 95 parseOnly: boolean;
96 textDocument: lc.TextDocumentIdentifier;
97 position: lc.Position;
98 selections: lc.Range[];
91} 99}
92export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr'); 100export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr');
93 101
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 5ceab8b44..bd99d696a 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -19,6 +19,16 @@ let ctx: Ctx | undefined;
19const RUST_PROJECT_CONTEXT_NAME = "inRustProject"; 19const RUST_PROJECT_CONTEXT_NAME = "inRustProject";
20 20
21export async function activate(context: vscode.ExtensionContext) { 21export async function activate(context: vscode.ExtensionContext) {
22 // For some reason vscode not always shows pop-up error notifications
23 // when an extension fails to activate, so we do it explicitly by ourselves.
24 // FIXME: remove this bit of code once vscode fixes this issue: https://github.com/microsoft/vscode/issues/101242
25 await tryActivate(context).catch(err => {
26 void vscode.window.showErrorMessage(`Cannot activate rust-analyzer: ${err.message}`);
27 throw err;
28 });
29}
30
31async function tryActivate(context: vscode.ExtensionContext) {
22 // Register a "dumb" onEnter command for the case where server fails to 32 // Register a "dumb" onEnter command for the case where server fails to
23 // start. 33 // start.
24 // 34 //
@@ -44,13 +54,13 @@ export async function activate(context: vscode.ExtensionContext) {
44 const serverPath = await bootstrap(config, state).catch(err => { 54 const serverPath = await bootstrap(config, state).catch(err => {
45 let message = "bootstrap error. "; 55 let message = "bootstrap error. ";
46 56
47 if (err.code === "EBUSY" || err.code === "ETXTBSY") { 57 if (err.code === "EBUSY" || err.code === "ETXTBSY" || err.code === "EPERM") {
48 message += "Other vscode windows might be using rust-analyzer, "; 58 message += "Other vscode windows might be using rust-analyzer, ";
49 message += "you should close them and reload this window to retry. "; 59 message += "you should close them and reload this window to retry. ";
50 } 60 }
51 61
52 message += 'Open "Help > Toggle Developer Tools > Console" to see the logs '; 62 message += 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). ';
53 message += '(enable verbose logs with "rust-analyzer.trace.extension")'; 63 message += 'To enable verbose logs use { "rust-analyzer.trace.extension": true }';
54 64
55 log.error("Bootstrap error", err); 65 log.error("Bootstrap error", err);
56 throw new Error(message); 66 throw new Error(message);
@@ -58,9 +68,7 @@ export async function activate(context: vscode.ExtensionContext) {
58 68
59 const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; 69 const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
60 if (workspaceFolder === undefined) { 70 if (workspaceFolder === undefined) {
61 const err = "Cannot activate rust-analyzer when no folder is opened"; 71 throw new Error("no folder is opened");
62 void vscode.window.showErrorMessage(err);
63 throw new Error(err);
64 } 72 }
65 73
66 // Note: we try to start the server before we activate type hints so that it 74 // Note: we try to start the server before we activate type hints so that it
@@ -88,7 +96,8 @@ export async function activate(context: vscode.ExtensionContext) {
88 }); 96 });
89 97
90 ctx.registerCommand('analyzerStatus', commands.analyzerStatus); 98 ctx.registerCommand('analyzerStatus', commands.analyzerStatus);
91 ctx.registerCommand('collectGarbage', commands.collectGarbage); 99 ctx.registerCommand('memoryUsage', commands.memoryUsage);
100 ctx.registerCommand('reloadWorkspace', commands.reloadWorkspace);
92 ctx.registerCommand('matchingBrace', commands.matchingBrace); 101 ctx.registerCommand('matchingBrace', commands.matchingBrace);
93 ctx.registerCommand('joinLines', commands.joinLines); 102 ctx.registerCommand('joinLines', commands.joinLines);
94 ctx.registerCommand('parentModule', commands.parentModule); 103 ctx.registerCommand('parentModule', commands.parentModule);
@@ -152,13 +161,17 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
152 return; 161 return;
153 }; 162 };
154 163
155 const lastCheck = state.lastCheck;
156 const now = Date.now(); 164 const now = Date.now();
165 if (config.package.releaseTag === NIGHTLY_TAG) {
166 // Check if we should poll github api for the new nightly version
167 // if we haven't done it during the past hour
168 const lastCheck = state.lastCheck;
157 169
158 const anHour = 60 * 60 * 1000; 170 const anHour = 60 * 60 * 1000;
159 const shouldDownloadNightly = state.releaseId === undefined || (now - (lastCheck ?? 0)) > anHour; 171 const shouldCheckForNewNightly = state.releaseId === undefined || (now - (lastCheck ?? 0)) > anHour;
160 172
161 if (!shouldDownloadNightly) return; 173 if (!shouldCheckForNewNightly) return;
174 }
162 175
163 const release = await fetchRelease("nightly").catch((e) => { 176 const release = await fetchRelease("nightly").catch((e) => {
164 log.error(e); 177 log.error(e);
@@ -202,7 +215,7 @@ async function bootstrapServer(config: Config, state: PersistentState): Promise<
202 ); 215 );
203 } 216 }
204 217
205 log.debug("Using server binary at", path); 218 log.info("Using server binary at", path);
206 219
207 if (!isValidExecutable(path)) { 220 if (!isValidExecutable(path)) {
208 throw new Error(`Failed to execute ${path} --version`); 221 throw new Error(`Failed to execute ${path} --version`);
@@ -261,13 +274,13 @@ async function getServer(config: Config, state: PersistentState): Promise<string
261 }; 274 };
262 if (config.package.releaseTag === null) return "rust-analyzer"; 275 if (config.package.releaseTag === null) return "rust-analyzer";
263 276
264 let binaryName: string | undefined = undefined; 277 let platform: string | undefined;
265 if (process.arch === "x64" || process.arch === "ia32") { 278 if (process.arch === "x64" || process.arch === "ia32") {
266 if (process.platform === "linux") binaryName = "rust-analyzer-linux"; 279 if (process.platform === "linux") platform = "linux";
267 if (process.platform === "darwin") binaryName = "rust-analyzer-mac"; 280 if (process.platform === "darwin") platform = "mac";
268 if (process.platform === "win32") binaryName = "rust-analyzer-windows.exe"; 281 if (process.platform === "win32") platform = "windows";
269 } 282 }
270 if (binaryName === undefined) { 283 if (platform === undefined) {
271 vscode.window.showErrorMessage( 284 vscode.window.showErrorMessage(
272 "Unfortunately we don't ship binaries for your platform yet. " + 285 "Unfortunately we don't ship binaries for your platform yet. " +
273 "You need to manually clone rust-analyzer repository and " + 286 "You need to manually clone rust-analyzer repository and " +
@@ -278,8 +291,8 @@ async function getServer(config: Config, state: PersistentState): Promise<string
278 ); 291 );
279 return undefined; 292 return undefined;
280 } 293 }
281 294 const ext = platform === "windows" ? ".exe" : "";
282 const dest = path.join(config.globalStoragePath, binaryName); 295 const dest = path.join(config.globalStoragePath, `rust-analyzer-${platform}${ext}`);
283 const exists = await fs.stat(dest).then(() => true, () => false); 296 const exists = await fs.stat(dest).then(() => true, () => false);
284 if (!exists) { 297 if (!exists) {
285 await state.updateServerVersion(undefined); 298 await state.updateServerVersion(undefined);
@@ -296,7 +309,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
296 } 309 }
297 310
298 const release = await fetchRelease(config.package.releaseTag); 311 const release = await fetchRelease(config.package.releaseTag);
299 const artifact = release.assets.find(artifact => artifact.name === binaryName); 312 const artifact = release.assets.find(artifact => artifact.name === `rust-analyzer-${platform}.gz`);
300 assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); 313 assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
301 314
302 // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error. 315 // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error.
@@ -308,6 +321,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
308 url: artifact.browser_download_url, 321 url: artifact.browser_download_url,
309 dest, 322 dest,
310 progressTitle: "Downloading rust-analyzer server", 323 progressTitle: "Downloading rust-analyzer server",
324 gunzip: true,
311 mode: 0o755 325 mode: 0o755
312 }); 326 });
313 327
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts
index 866092882..681eaa9c9 100644
--- a/editors/code/src/net.ts
+++ b/editors/code/src/net.ts
@@ -1,8 +1,12 @@
1import fetch from "node-fetch"; 1// Replace with `import fetch from "node-fetch"` once this is fixed in rollup:
2// https://github.com/rollup/plugins/issues/491
3const fetch = require("node-fetch") as typeof import("node-fetch")["default"];
4
2import * as vscode from "vscode"; 5import * as vscode from "vscode";
3import * as stream from "stream"; 6import * as stream from "stream";
4import * as crypto from "crypto"; 7import * as crypto from "crypto";
5import * as fs from "fs"; 8import * as fs from "fs";
9import * as zlib from "zlib";
6import * as util from "util"; 10import * as util from "util";
7import * as path from "path"; 11import * as path from "path";
8import { log, assert } from "./util"; 12import { log, assert } from "./util";
@@ -65,6 +69,7 @@ interface DownloadOpts {
65 url: string; 69 url: string;
66 dest: string; 70 dest: string;
67 mode?: number; 71 mode?: number;
72 gunzip?: boolean;
68} 73}
69 74
70export async function download(opts: DownloadOpts) { 75export async function download(opts: DownloadOpts) {
@@ -82,7 +87,7 @@ export async function download(opts: DownloadOpts) {
82 }, 87 },
83 async (progress, _cancellationToken) => { 88 async (progress, _cancellationToken) => {
84 let lastPercentage = 0; 89 let lastPercentage = 0;
85 await downloadFile(opts.url, tempFile, opts.mode, (readBytes, totalBytes) => { 90 await downloadFile(opts.url, tempFile, opts.mode, !!opts.gunzip, (readBytes, totalBytes) => {
86 const newPercentage = (readBytes / totalBytes) * 100; 91 const newPercentage = (readBytes / totalBytes) * 100;
87 progress.report({ 92 progress.report({
88 message: newPercentage.toFixed(0) + "%", 93 message: newPercentage.toFixed(0) + "%",
@@ -97,16 +102,11 @@ export async function download(opts: DownloadOpts) {
97 await fs.promises.rename(tempFile, opts.dest); 102 await fs.promises.rename(tempFile, opts.dest);
98} 103}
99 104
100/**
101 * Downloads file from `url` and stores it at `destFilePath` with `mode` (unix permissions).
102 * `onProgress` callback is called on recieveing each chunk of bytes
103 * to track the progress of downloading, it gets the already read and total
104 * amount of bytes to read as its parameters.
105 */
106async function downloadFile( 105async function downloadFile(
107 url: string, 106 url: string,
108 destFilePath: fs.PathLike, 107 destFilePath: fs.PathLike,
109 mode: number | undefined, 108 mode: number | undefined,
109 gunzip: boolean,
110 onProgress: (readBytes: number, totalBytes: number) => void 110 onProgress: (readBytes: number, totalBytes: number) => void
111): Promise<void> { 111): Promise<void> {
112 const res = await fetch(url); 112 const res = await fetch(url);
@@ -130,7 +130,10 @@ async function downloadFile(
130 }); 130 });
131 131
132 const destFileStream = fs.createWriteStream(destFilePath, { mode }); 132 const destFileStream = fs.createWriteStream(destFilePath, { mode });
133 await pipeline(res.body, destFileStream); 133 const srcStream = gunzip ? res.body.pipe(zlib.createGunzip()) : res.body;
134
135 await pipeline(srcStream, destFileStream);
136
134 await new Promise<void>(resolve => { 137 await new Promise<void>(resolve => {
135 destFileStream.on("close", resolve); 138 destFileStream.on("close", resolve);
136 destFileStream.destroy(); 139 destFileStream.destroy();
diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts
index 138d11b89..5705eed81 100644
--- a/editors/code/src/persistent_state.ts
+++ b/editors/code/src/persistent_state.ts
@@ -4,7 +4,7 @@ import { log } from './util';
4export class PersistentState { 4export class PersistentState {
5 constructor(private readonly globalState: vscode.Memento) { 5 constructor(private readonly globalState: vscode.Memento) {
6 const { lastCheck, releaseId, serverVersion } = this; 6 const { lastCheck, releaseId, serverVersion } = this;
7 log.debug("PersistentState: ", { lastCheck, releaseId, serverVersion }); 7 log.info("PersistentState:", { lastCheck, releaseId, serverVersion });
8 } 8 }
9 9
10 /** 10 /**
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index 766b05112..de68f27ae 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -5,7 +5,7 @@ import * as tasks from './tasks';
5 5
6import { Ctx } from './ctx'; 6import { Ctx } from './ctx';
7import { makeDebugConfig } from './debug'; 7import { makeDebugConfig } from './debug';
8import { Config } from './config'; 8import { Config, RunnableEnvCfg } from './config';
9 9
10const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; 10const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
11 11
@@ -96,6 +96,30 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
96 } 96 }
97} 97}
98 98
99export function prepareEnv(runnable: ra.Runnable, runnableEnvCfg: RunnableEnvCfg): Record<string, string> {
100 const env: Record<string, string> = { "RUST_BACKTRACE": "short" };
101
102 if (runnable.args.expectTest) {
103 env["UPDATE_EXPECT"] = "1";
104 }
105
106 Object.assign(env, process.env as { [key: string]: string });
107
108 if (runnableEnvCfg) {
109 if (Array.isArray(runnableEnvCfg)) {
110 for (const it of runnableEnvCfg) {
111 if (!it.mask || new RegExp(it.mask).test(runnable.label)) {
112 Object.assign(env, it.env);
113 }
114 }
115 } else {
116 Object.assign(env, runnableEnvCfg);
117 }
118 }
119
120 return env;
121}
122
99export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> { 123export async function createTask(runnable: ra.Runnable, config: Config): Promise<vscode.Task> {
100 if (runnable.kind !== "cargo") { 124 if (runnable.kind !== "cargo") {
101 // rust-analyzer supports only one kind, "cargo" 125 // rust-analyzer supports only one kind, "cargo"
@@ -108,12 +132,13 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
108 if (runnable.args.executableArgs.length > 0) { 132 if (runnable.args.executableArgs.length > 0) {
109 args.push('--', ...runnable.args.executableArgs); 133 args.push('--', ...runnable.args.executableArgs);
110 } 134 }
135
111 const definition: tasks.CargoTaskDefinition = { 136 const definition: tasks.CargoTaskDefinition = {
112 type: tasks.TASK_TYPE, 137 type: tasks.TASK_TYPE,
113 command: args[0], // run, test, etc... 138 command: args[0], // run, test, etc...
114 args: args.slice(1), 139 args: args.slice(1),
115 cwd: runnable.args.workspaceRoot, 140 cwd: runnable.args.workspaceRoot || ".",
116 env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }), 141 env: prepareEnv(runnable, config.runnableEnv),
117 }; 142 };
118 143
119 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() 144 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts
index bcb3f2cc7..258b49982 100644
--- a/editors/code/src/snippets.ts
+++ b/editors/code/src/snippets.ts
@@ -6,6 +6,10 @@ export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
6 assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`); 6 assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`);
7 const [uri, edits] = edit.entries()[0]; 7 const [uri, edits] = edit.entries()[0];
8 8
9 if (vscode.window.activeTextEditor?.document.uri !== uri) {
10 // `vscode.window.visibleTextEditors` only contains editors whose contents are being displayed
11 await vscode.window.showTextDocument(uri, {});
12 }
9 const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString()); 13 const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString());
10 if (!editor) return; 14 if (!editor) return;
11 await applySnippetTextEdits(editor, edits); 15 await applySnippetTextEdits(editor, edits);
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index fec4c3295..970fedb37 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -2,6 +2,7 @@ import * as lc from "vscode-languageclient";
2import * as vscode from "vscode"; 2import * as vscode from "vscode";
3import { strict as nativeAssert } from "assert"; 3import { strict as nativeAssert } from "assert";
4import { spawnSync } from "child_process"; 4import { spawnSync } from "child_process";
5import { inspect } from "util";
5 6
6export function assert(condition: boolean, explanation: string): asserts condition { 7export function assert(condition: boolean, explanation: string): asserts condition {
7 try { 8 try {
@@ -14,21 +15,46 @@ export function assert(condition: boolean, explanation: string): asserts conditi
14 15
15export const log = new class { 16export const log = new class {
16 private enabled = true; 17 private enabled = true;
18 private readonly output = vscode.window.createOutputChannel("Rust Analyzer Client");
17 19
18 setEnabled(yes: boolean): void { 20 setEnabled(yes: boolean): void {
19 log.enabled = yes; 21 log.enabled = yes;
20 } 22 }
21 23
22 debug(message?: any, ...optionalParams: any[]): void { 24 // Hint: the type [T, ...T[]] means a non-empty array
25 debug(...msg: [unknown, ...unknown[]]): void {
23 if (!log.enabled) return; 26 if (!log.enabled) return;
24 // eslint-disable-next-line no-console 27 log.write("DEBUG", ...msg);
25 console.log(message, ...optionalParams); 28 log.output.toString();
26 } 29 }
27 30
28 error(message?: any, ...optionalParams: any[]): void { 31 info(...msg: [unknown, ...unknown[]]): void {
32 log.write("INFO", ...msg);
33 }
34
35 warn(...msg: [unknown, ...unknown[]]): void {
36 debugger;
37 log.write("WARN", ...msg);
38 }
39
40 error(...msg: [unknown, ...unknown[]]): void {
29 debugger; 41 debugger;
30 // eslint-disable-next-line no-console 42 log.write("ERROR", ...msg);
31 console.error(message, ...optionalParams); 43 log.output.show(true);
44 }
45
46 private write(label: string, ...messageParts: unknown[]): void {
47 const message = messageParts.map(log.stringify).join(" ");
48 const dateTime = new Date().toLocaleString();
49 log.output.appendLine(`${label} [${dateTime}]: ${message}`);
50 }
51
52 private stringify(val: unknown): string {
53 if (typeof val === "string") return val;
54 return inspect(val, {
55 colors: false,
56 depth: 6, // heuristic
57 });
32 } 58 }
33}; 59};
34 60
@@ -46,7 +72,7 @@ export async function sendRequestWithRetry<TParam, TRet>(
46 ); 72 );
47 } catch (error) { 73 } catch (error) {
48 if (delay === null) { 74 if (delay === null) {
49 log.error("LSP request timed out", { method: reqType.method, param, error }); 75 log.warn("LSP request timed out", { method: reqType.method, param, error });
50 throw error; 76 throw error;
51 } 77 }
52 78
@@ -55,7 +81,7 @@ export async function sendRequestWithRetry<TParam, TRet>(
55 } 81 }
56 82
57 if (error.code !== lc.ErrorCodes.ContentModified) { 83 if (error.code !== lc.ErrorCodes.ContentModified) {
58 log.error("LSP request failed", { method: reqType.method, param, error }); 84 log.warn("LSP request failed", { method: reqType.method, param, error });
59 throw error; 85 throw error;
60 } 86 }
61 87
@@ -89,7 +115,8 @@ export function isValidExecutable(path: string): boolean {
89 115
90 const res = spawnSync(path, ["--version"], { encoding: 'utf8' }); 116 const res = spawnSync(path, ["--version"], { encoding: 'utf8' });
91 117
92 log.debug(res, "--version output:", res.output); 118 const printOutput = res.error && (res.error as any).code !== 'ENOENT' ? log.warn : log.debug;
119 printOutput(path, "--version:", res);
93 120
94 return res.status === 0; 121 return res.status === 0;
95} 122}