aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src')
-rw-r--r--editors/code/src/client.ts23
-rw-r--r--editors/code/src/commands.ts32
-rw-r--r--editors/code/src/config.ts1
-rw-r--r--editors/code/src/ctx.ts6
-rw-r--r--editors/code/src/debug.ts12
-rw-r--r--editors/code/src/inlay_hints.ts2
-rw-r--r--editors/code/src/lsp_ext.ts2
-rw-r--r--editors/code/src/main.ts36
-rw-r--r--editors/code/src/net.ts25
-rw-r--r--editors/code/src/run.ts26
-rw-r--r--editors/code/src/snippets.ts4
11 files changed, 93 insertions, 76 deletions
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 539e487ec..0771ca3b6 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -2,7 +2,6 @@ import * as lc from 'vscode-languageclient/node';
2import * as vscode from 'vscode'; 2import * as vscode from 'vscode';
3import * as ra from '../src/lsp_ext'; 3import * as ra from '../src/lsp_ext';
4import * as Is from 'vscode-languageclient/lib/common/utils/is'; 4import * as Is from 'vscode-languageclient/lib/common/utils/is';
5import { DocumentSemanticsTokensSignature, DocumentSemanticsTokensEditsSignature, DocumentRangeSemanticTokensSignature } from 'vscode-languageclient/lib/common/semanticTokens';
6import { assert } from './util'; 5import { assert } from './util';
7import { WorkspaceEdit } from 'vscode'; 6import { WorkspaceEdit } from 'vscode';
8 7
@@ -11,7 +10,7 @@ export interface Env {
11} 10}
12 11
13function renderCommand(cmd: ra.CommandLink) { 12function renderCommand(cmd: ra.CommandLink) {
14 return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`; 13 return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip}')`;
15} 14}
16 15
17function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString { 16function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString {
@@ -24,13 +23,6 @@ function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownStri
24 return result; 23 return result;
25} 24}
26 25
27// Workaround for https://github.com/microsoft/vscode-languageserver-node/issues/576
28async function semanticHighlightingWorkaround<R, F extends (...args: any[]) => vscode.ProviderResult<R>>(next: F, ...args: Parameters<F>): Promise<R> {
29 const res = await next(...args);
30 if (res == null) throw new Error('busy');
31 return res;
32}
33
34export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc.LanguageClient { 26export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc.LanguageClient {
35 // '.' Is the fallback if no folder is open 27 // '.' Is the fallback if no folder is open
36 // TODO?: Workspace folders support Uri's (eg: file://test.txt). 28 // TODO?: Workspace folders support Uri's (eg: file://test.txt).
@@ -57,15 +49,6 @@ export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc
57 diagnosticCollectionName: "rustc", 49 diagnosticCollectionName: "rustc",
58 traceOutputChannel, 50 traceOutputChannel,
59 middleware: { 51 middleware: {
60 provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken, next: DocumentSemanticsTokensSignature): vscode.ProviderResult<vscode.SemanticTokens> {
61 return semanticHighlightingWorkaround(next, document, token);
62 },
63 provideDocumentSemanticTokensEdits(document: vscode.TextDocument, previousResultId: string, token: vscode.CancellationToken, next: DocumentSemanticsTokensEditsSignature): vscode.ProviderResult<vscode.SemanticTokensEdits | vscode.SemanticTokens> {
64 return semanticHighlightingWorkaround(next, document, previousResultId, token);
65 },
66 provideDocumentRangeSemanticTokens(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken, next: DocumentRangeSemanticTokensSignature): vscode.ProviderResult<vscode.SemanticTokens> {
67 return semanticHighlightingWorkaround(next, document, range, token);
68 },
69 async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) { 52 async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) {
70 return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then( 53 return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then(
71 (result) => { 54 (result) => {
@@ -79,7 +62,7 @@ export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc
79 return hover; 62 return hover;
80 }, 63 },
81 (error) => { 64 (error) => {
82 client.handleFailedRequest(lc.HoverRequest.type, error, null); 65 client.handleFailedRequest(lc.HoverRequest.type, token, error, null);
83 return Promise.resolve(null); 66 return Promise.resolve(null);
84 }); 67 });
85 }, 68 },
@@ -138,7 +121,7 @@ export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc
138 command: "rust-analyzer.applyActionGroup", 121 command: "rust-analyzer.applyActionGroup",
139 title: "", 122 title: "",
140 arguments: [items.map((item) => { 123 arguments: [items.map((item) => {
141 return { label: item.title, arguments: item.command!!.arguments!![0] }; 124 return { label: item.title, arguments: item.command!.arguments![0] };
142 })], 125 })],
143 }; 126 };
144 127
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index c1c9f9754..283b9a160 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -5,7 +5,7 @@ import * as ra from './lsp_ext';
5import { Ctx, Cmd } from './ctx'; 5import { Ctx, Cmd } from './ctx';
6import { applySnippetWorkspaceEdit, applySnippetTextEdits } from './snippets'; 6import { applySnippetWorkspaceEdit, applySnippetTextEdits } from './snippets';
7import { spawnSync } from 'child_process'; 7import { spawnSync } from 'child_process';
8import { RunnableQuickPick, selectRunnable, createTask } from './run'; 8import { RunnableQuickPick, selectRunnable, createTask, createArgs } from './run';
9import { AstInspector } from './ast_inspector'; 9import { AstInspector } from './ast_inspector';
10import { isRustDocument, sleep, isRustEditor } from './util'; 10import { isRustDocument, sleep, isRustEditor } from './util';
11import { startDebugSession, makeDebugConfig } from './debug'; 11import { startDebugSession, makeDebugConfig } from './debug';
@@ -125,7 +125,7 @@ export function joinLines(ctx: Ctx): Cmd {
125 ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)), 125 ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
126 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), 126 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
127 }); 127 });
128 editor.edit((builder) => { 128 await editor.edit((builder) => {
129 client.protocol2CodeConverter.asTextEdits(items).forEach((edit: any) => { 129 client.protocol2CodeConverter.asTextEdits(items).forEach((edit: any) => {
130 builder.replace(edit.range, edit.newText); 130 builder.replace(edit.range, edit.newText);
131 }); 131 });
@@ -236,7 +236,7 @@ export function ssr(ctx: Ctx): Cmd {
236 const request = await vscode.window.showInputBox(options); 236 const request = await vscode.window.showInputBox(options);
237 if (!request) return; 237 if (!request) return;
238 238
239 vscode.window.withProgress({ 239 await vscode.window.withProgress({
240 location: vscode.ProgressLocation.Notification, 240 location: vscode.ProgressLocation.Notification,
241 title: "Structured search replace in progress...", 241 title: "Structured search replace in progress...",
242 cancellable: false, 242 cancellable: false,
@@ -457,10 +457,10 @@ export function reloadWorkspace(ctx: Ctx): Cmd {
457} 457}
458 458
459export function showReferences(ctx: Ctx): Cmd { 459export function showReferences(ctx: Ctx): Cmd {
460 return (uri: string, position: lc.Position, locations: lc.Location[]) => { 460 return async (uri: string, position: lc.Position, locations: lc.Location[]) => {
461 const client = ctx.client; 461 const client = ctx.client;
462 if (client) { 462 if (client) {
463 vscode.commands.executeCommand( 463 await vscode.commands.executeCommand(
464 'editor.action.showReferences', 464 'editor.action.showReferences',
465 vscode.Uri.parse(uri), 465 vscode.Uri.parse(uri),
466 client.protocol2CodeConverter.asPosition(position), 466 client.protocol2CodeConverter.asPosition(position),
@@ -474,7 +474,7 @@ export function applyActionGroup(_ctx: Ctx): Cmd {
474 return async (actions: { label: string; arguments: lc.CodeAction }[]) => { 474 return async (actions: { label: string; arguments: lc.CodeAction }[]) => {
475 const selectedAction = await vscode.window.showQuickPick(actions); 475 const selectedAction = await vscode.window.showQuickPick(actions);
476 if (!selectedAction) return; 476 if (!selectedAction) return;
477 vscode.commands.executeCommand( 477 await vscode.commands.executeCommand(
478 'rust-analyzer.resolveCodeAction', 478 'rust-analyzer.resolveCodeAction',
479 selectedAction.arguments, 479 selectedAction.arguments,
480 ); 480 );
@@ -510,7 +510,7 @@ export function openDocs(ctx: Ctx): Cmd {
510 const doclink = await client.sendRequest(ra.openDocs, { position, textDocument }); 510 const doclink = await client.sendRequest(ra.openDocs, { position, textDocument });
511 511
512 if (doclink != null) { 512 if (doclink != null) {
513 vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(doclink)); 513 await vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(doclink));
514 } 514 }
515 }; 515 };
516 516
@@ -528,10 +528,10 @@ export function resolveCodeAction(ctx: Ctx): Cmd {
528 const edit = client.protocol2CodeConverter.asWorkspaceEdit(itemEdit); 528 const edit = client.protocol2CodeConverter.asWorkspaceEdit(itemEdit);
529 // filter out all text edits and recreate the WorkspaceEdit without them so we can apply 529 // filter out all text edits and recreate the WorkspaceEdit without them so we can apply
530 // snippet edits on our own 530 // snippet edits on our own
531 const itemEditWithoutTextEdits = { ...item, documentChanges: itemEdit.documentChanges?.filter(change => "kind" in change) }; 531 const lcFileSystemEdit = { ...itemEdit, documentChanges: itemEdit.documentChanges?.filter(change => "kind" in change) };
532 const editWithoutTextEdits = client.protocol2CodeConverter.asWorkspaceEdit(itemEditWithoutTextEdits); 532 const fileSystemEdit = client.protocol2CodeConverter.asWorkspaceEdit(lcFileSystemEdit);
533 await vscode.workspace.applyEdit(fileSystemEdit);
533 await applySnippetWorkspaceEdit(edit); 534 await applySnippetWorkspaceEdit(edit);
534 await vscode.workspace.applyEdit(editWithoutTextEdits);
535 }; 535 };
536} 536}
537 537
@@ -572,6 +572,18 @@ export function runSingle(ctx: Ctx): Cmd {
572 }; 572 };
573} 573}
574 574
575export function copyRunCommandLine(ctx: Ctx) {
576 let prevRunnable: RunnableQuickPick | undefined;
577 return async () => {
578 const item = await selectRunnable(ctx, prevRunnable);
579 if (!item) return;
580 const args = createArgs(item.runnable);
581 const commandLine = ["cargo", ...args].join(" ");
582 await vscode.env.clipboard.writeText(commandLine);
583 await vscode.window.showInformationMessage("Cargo invocation copied to the clipboard.");
584 };
585}
586
575export function debug(ctx: Ctx): Cmd { 587export function debug(ctx: Ctx): Cmd {
576 let prevDebuggee: RunnableQuickPick | undefined; 588 let prevDebuggee: RunnableQuickPick | undefined;
577 589
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index ebe4de1ea..ddb5cfbd3 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -144,6 +144,7 @@ export class Config {
144 debug: this.get<boolean>("lens.debug"), 144 debug: this.get<boolean>("lens.debug"),
145 implementations: this.get<boolean>("lens.implementations"), 145 implementations: this.get<boolean>("lens.implementations"),
146 methodReferences: this.get<boolean>("lens.methodReferences"), 146 methodReferences: this.get<boolean>("lens.methodReferences"),
147 references: this.get<boolean>("lens.references"),
147 }; 148 };
148 } 149 }
149 150
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index e7585184b..c07583cfa 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -74,6 +74,12 @@ export class Ctx {
74 this.statusBar.command = undefined; 74 this.statusBar.command = undefined;
75 this.statusBar.color = undefined; 75 this.statusBar.color = undefined;
76 break; 76 break;
77 case "readyPartial":
78 this.statusBar.text = "rust-analyzer";
79 this.statusBar.tooltip = "Ready (Partial)";
80 this.statusBar.command = undefined;
81 this.statusBar.color = undefined;
82 break;
77 case "ready": 83 case "ready":
78 this.statusBar.text = "rust-analyzer"; 84 this.statusBar.text = "rust-analyzer";
79 this.statusBar.tooltip = "Ready"; 85 this.statusBar.tooltip = "Ready";
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index 925126a16..3889a2773 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -77,7 +77,7 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
77 } 77 }
78 78
79 if (!debugEngine) { 79 if (!debugEngine) {
80 vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` 80 await vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)`
81 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`); 81 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`);
82 return; 82 return;
83 } 83 }
@@ -86,12 +86,14 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
86 if (ctx.config.debug.openDebugPane) { 86 if (ctx.config.debug.openDebugPane) {
87 debugOutput.show(true); 87 debugOutput.show(true);
88 } 88 }
89 89 // folder exists or RA is not active.
90 const isMultiFolderWorkspace = vscode.workspace.workspaceFolders!.length > 1; 90 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
91 const firstWorkspace = vscode.workspace.workspaceFolders![0]; // folder exists or RA is not active. 91 const workspaceFolders = vscode.workspace.workspaceFolders!;
92 const isMultiFolderWorkspace = workspaceFolders.length > 1;
93 const firstWorkspace = workspaceFolders[0];
92 const workspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ? 94 const workspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ?
93 firstWorkspace : 95 firstWorkspace :
94 vscode.workspace.workspaceFolders!.find(w => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace; 96 workspaceFolders.find(w => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace;
95 97
96 const wsFolder = path.normalize(workspace.uri.fsPath); 98 const wsFolder = path.normalize(workspace.uri.fsPath);
97 const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : ''; 99 const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : '';
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts
index 38eb1c15b..61db6b8d0 100644
--- a/editors/code/src/inlay_hints.ts
+++ b/editors/code/src/inlay_hints.ts
@@ -36,7 +36,7 @@ export function activateInlayHints(ctx: Ctx) {
36 maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions 36 maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions
37 ); 37 );
38 38
39 maybeUpdater.onConfigChange(); 39 maybeUpdater.onConfigChange().catch(console.error);
40} 40}
41 41
42const typeHints = createHintStyle("type"); 42const typeHints = createHintStyle("type");
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index d21a3db86..2de1e427d 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -10,7 +10,7 @@ export interface AnalyzerStatusParams {
10export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus"); 10export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus");
11export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage"); 11export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage");
12 12
13export type Status = "loading" | "ready" | "invalid" | "needsReload"; 13export type Status = "loading" | "ready" | "readyPartial" | "invalid" | "needsReload";
14export interface StatusParams { 14export interface StatusParams {
15 status: Status; 15 status: Status;
16} 16}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 1edb7713d..00393d6e8 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -12,7 +12,7 @@ import { PersistentState } from './persistent_state';
12import { fetchRelease, download } from './net'; 12import { fetchRelease, download } from './net';
13import { activateTaskProvider } from './tasks'; 13import { activateTaskProvider } from './tasks';
14import { setContextValue } from './util'; 14import { setContextValue } from './util';
15import { exec } from 'child_process'; 15import { exec, spawnSync } from 'child_process';
16 16
17let ctx: Ctx | undefined; 17let ctx: Ctx | undefined;
18 18
@@ -76,7 +76,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
76 // This a horribly, horribly wrong way to deal with this problem. 76 // This a horribly, horribly wrong way to deal with this problem.
77 ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath); 77 ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath);
78 78
79 setContextValue(RUST_PROJECT_CONTEXT_NAME, true); 79 await setContextValue(RUST_PROJECT_CONTEXT_NAME, true);
80 80
81 // Commands which invokes manually via command palette, shortcut, etc. 81 // Commands which invokes manually via command palette, shortcut, etc.
82 82
@@ -108,6 +108,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
108 ctx.registerCommand('viewHir', commands.viewHir); 108 ctx.registerCommand('viewHir', commands.viewHir);
109 ctx.registerCommand('expandMacro', commands.expandMacro); 109 ctx.registerCommand('expandMacro', commands.expandMacro);
110 ctx.registerCommand('run', commands.run); 110 ctx.registerCommand('run', commands.run);
111 ctx.registerCommand('copyRunCommandLine', commands.copyRunCommandLine);
111 ctx.registerCommand('debug', commands.debug); 112 ctx.registerCommand('debug', commands.debug);
112 ctx.registerCommand('newDebugConfig', commands.newDebugConfig); 113 ctx.registerCommand('newDebugConfig', commands.newDebugConfig);
113 ctx.registerCommand('openDocs', commands.openDocs); 114 ctx.registerCommand('openDocs', commands.openDocs);
@@ -142,7 +143,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
142} 143}
143 144
144export async function deactivate() { 145export async function deactivate() {
145 setContextValue(RUST_PROJECT_CONTEXT_NAME, undefined); 146 await setContextValue(RUST_PROJECT_CONTEXT_NAME, undefined);
146 await ctx?.client.stop(); 147 await ctx?.client.stop();
147 ctx = undefined; 148 ctx = undefined;
148} 149}
@@ -183,10 +184,10 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
183 184
184 const release = await downloadWithRetryDialog(state, async () => { 185 const release = await downloadWithRetryDialog(state, async () => {
185 return await fetchRelease("nightly", state.githubToken); 186 return await fetchRelease("nightly", state.githubToken);
186 }).catch((e) => { 187 }).catch(async (e) => {
187 log.error(e); 188 log.error(e);
188 if (state.releaseId === undefined) { // Show error only for the initial download 189 if (state.releaseId === undefined) { // Show error only for the initial download
189 vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`); 190 await vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`);
190 } 191 }
191 return undefined; 192 return undefined;
192 }); 193 });
@@ -208,7 +209,6 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
208 url: artifact.browser_download_url, 209 url: artifact.browser_download_url,
209 dest, 210 dest,
210 progressTitle: "Downloading rust-analyzer extension", 211 progressTitle: "Downloading rust-analyzer extension",
211 overwrite: true,
212 }); 212 });
213 }); 213 });
214 214
@@ -246,10 +246,10 @@ async function patchelf(dest: PathLike): Promise<void> {
246 }, 246 },
247 async (progress, _) => { 247 async (progress, _) => {
248 const expression = ` 248 const expression = `
249 {src, pkgs ? import <nixpkgs> {}}: 249 {srcStr, pkgs ? import <nixpkgs> {}}:
250 pkgs.stdenv.mkDerivation { 250 pkgs.stdenv.mkDerivation {
251 name = "rust-analyzer"; 251 name = "rust-analyzer";
252 inherit src; 252 src = /. + srcStr;
253 phases = [ "installPhase" "fixupPhase" ]; 253 phases = [ "installPhase" "fixupPhase" ];
254 installPhase = "cp $src $out"; 254 installPhase = "cp $src $out";
255 fixupPhase = '' 255 fixupPhase = ''
@@ -262,7 +262,7 @@ async function patchelf(dest: PathLike): Promise<void> {
262 await fs.rename(dest, origFile); 262 await fs.rename(dest, origFile);
263 progress.report({ message: "Patching executable", increment: 20 }); 263 progress.report({ message: "Patching executable", increment: 20 });
264 await new Promise((resolve, reject) => { 264 await new Promise((resolve, reject) => {
265 const handle = exec(`nix-build -E - --arg src '${origFile}' -o ${dest}`, 265 const handle = exec(`nix-build -E - --argstr srcStr '${origFile}' -o '${dest}'`,
266 (err, stdout, stderr) => { 266 (err, stdout, stderr) => {
267 if (err != null) { 267 if (err != null) {
268 reject(Error(stderr)); 268 reject(Error(stderr));
@@ -297,9 +297,9 @@ async function getServer(config: Config, state: PersistentState): Promise<string
297 "arm64 linux": "aarch64-unknown-linux-gnu", 297 "arm64 linux": "aarch64-unknown-linux-gnu",
298 "arm64 darwin": "aarch64-apple-darwin", 298 "arm64 darwin": "aarch64-apple-darwin",
299 }; 299 };
300 const platform = platforms[`${process.arch} ${process.platform}`]; 300 let platform = platforms[`${process.arch} ${process.platform}`];
301 if (platform === undefined) { 301 if (platform === undefined) {
302 vscode.window.showErrorMessage( 302 await vscode.window.showErrorMessage(
303 "Unfortunately we don't ship binaries for your platform yet. " + 303 "Unfortunately we don't ship binaries for your platform yet. " +
304 "You need to manually clone rust-analyzer repository and " + 304 "You need to manually clone rust-analyzer repository and " +
305 "run `cargo xtask install --server` to build the language server from sources. " + 305 "run `cargo xtask install --server` to build the language server from sources. " +
@@ -309,6 +309,9 @@ async function getServer(config: Config, state: PersistentState): Promise<string
309 ); 309 );
310 return undefined; 310 return undefined;
311 } 311 }
312 if (platform === "x86_64-unknown-linux-gnu" && isMusl()) {
313 platform = "x86_64-unknown-linux-musl";
314 }
312 const ext = platform.indexOf("-windows-") !== -1 ? ".exe" : ""; 315 const ext = platform.indexOf("-windows-") !== -1 ? ".exe" : "";
313 const dest = path.join(config.globalStoragePath, `rust-analyzer-${platform}${ext}`); 316 const dest = path.join(config.globalStoragePath, `rust-analyzer-${platform}${ext}`);
314 const exists = await fs.stat(dest).then(() => true, () => false); 317 const exists = await fs.stat(dest).then(() => true, () => false);
@@ -340,7 +343,6 @@ async function getServer(config: Config, state: PersistentState): Promise<string
340 progressTitle: "Downloading rust-analyzer server", 343 progressTitle: "Downloading rust-analyzer server",
341 gunzip: true, 344 gunzip: true,
342 mode: 0o755, 345 mode: 0o755,
343 overwrite: true,
344 }); 346 });
345 }); 347 });
346 348
@@ -366,6 +368,13 @@ async function isNixOs(): Promise<boolean> {
366 } 368 }
367} 369}
368 370
371function isMusl(): boolean {
372 // We can detect Alpine by checking `/etc/os-release` but not Void Linux musl.
373 // Instead, we run `ldd` since it advertises the libc which it belongs to.
374 const res = spawnSync("ldd", ["--version"]);
375 return res.stderr != null && res.stderr.indexOf("musl libc") >= 0;
376}
377
369async function downloadWithRetryDialog<T>(state: PersistentState, downloadFunc: () => Promise<T>): Promise<T> { 378async function downloadWithRetryDialog<T>(state: PersistentState, downloadFunc: () => Promise<T>): Promise<T> {
370 while (true) { 379 while (true) {
371 try { 380 try {
@@ -435,6 +444,7 @@ function warnAboutExtensionConflicts() {
435 vscode.window.showWarningMessage( 444 vscode.window.showWarningMessage(
436 `You have both the ${fst[0]} (${fst[1]}) and ${sec[0]} (${sec[1]}) ` + 445 `You have both the ${fst[0]} (${fst[1]}) and ${sec[0]} (${sec[1]}) ` +
437 "plugins enabled. These are known to conflict and cause various functions of " + 446 "plugins enabled. These are known to conflict and cause various functions of " +
438 "both plugins to not work correctly. You should disable one of them.", "Got it"); 447 "both plugins to not work correctly. You should disable one of them.", "Got it")
448 .then(() => { }, console.error);
439 }; 449 };
440} 450}
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts
index 1ab21e726..d39dc1baf 100644
--- a/editors/code/src/net.ts
+++ b/editors/code/src/net.ts
@@ -73,23 +73,16 @@ interface DownloadOpts {
73 dest: string; 73 dest: string;
74 mode?: number; 74 mode?: number;
75 gunzip?: boolean; 75 gunzip?: boolean;
76 overwrite?: boolean;
77} 76}
78 77
79export async function download(opts: DownloadOpts) { 78export async function download(opts: DownloadOpts) {
80 // Put artifact into a temporary file (in the same dir for simplicity) 79 // Put artifact into a temporary file (in the same dir for simplicity)
81 // to prevent partially downloaded files when user kills vscode 80 // to prevent partially downloaded files when user kills vscode
81 // This also avoids overwriting running executables
82 const dest = path.parse(opts.dest); 82 const dest = path.parse(opts.dest);
83 const randomHex = crypto.randomBytes(5).toString("hex"); 83 const randomHex = crypto.randomBytes(5).toString("hex");
84 const tempFile = path.join(dest.dir, `${dest.name}${randomHex}`); 84 const tempFile = path.join(dest.dir, `${dest.name}${randomHex}`);
85 85
86 if (opts.overwrite) {
87 // Unlinking the exe file before moving new one on its place should prevent ETXTBSY error.
88 await fs.promises.unlink(opts.dest).catch(err => {
89 if (err.code !== "ENOENT") throw err;
90 });
91 }
92
93 await vscode.window.withProgress( 86 await vscode.window.withProgress(
94 { 87 {
95 location: vscode.ProgressLocation.Notification, 88 location: vscode.ProgressLocation.Notification,
@@ -99,13 +92,15 @@ export async function download(opts: DownloadOpts) {
99 async (progress, _cancellationToken) => { 92 async (progress, _cancellationToken) => {
100 let lastPercentage = 0; 93 let lastPercentage = 0;
101 await downloadFile(opts.url, tempFile, opts.mode, !!opts.gunzip, (readBytes, totalBytes) => { 94 await downloadFile(opts.url, tempFile, opts.mode, !!opts.gunzip, (readBytes, totalBytes) => {
102 const newPercentage = (readBytes / totalBytes) * 100; 95 const newPercentage = Math.round((readBytes / totalBytes) * 100);
103 progress.report({ 96 if (newPercentage !== lastPercentage) {
104 message: newPercentage.toFixed(0) + "%", 97 progress.report({
105 increment: newPercentage - lastPercentage 98 message: `${newPercentage.toFixed(0)}%`,
106 }); 99 increment: newPercentage - lastPercentage
107 100 });
108 lastPercentage = newPercentage; 101
102 lastPercentage = newPercentage;
103 }
109 }); 104 });
110 } 105 }
111 ); 106 );
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index 17573cd82..138e3f686 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -45,7 +45,7 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick,
45 if (items.length === 0) { 45 if (items.length === 0) {
46 // it is the debug case, run always has at least 'cargo check ...' 46 // it is the debug case, run always has at least 'cargo check ...'
47 // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables 47 // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables
48 vscode.window.showErrorMessage("There's no debug target!"); 48 await vscode.window.showErrorMessage("There's no debug target!");
49 return; 49 return;
50 } 50 }
51 51
@@ -65,8 +65,8 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick,
65 disposables.push( 65 disposables.push(
66 quickPick.onDidHide(() => close()), 66 quickPick.onDidHide(() => close()),
67 quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), 67 quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
68 quickPick.onDidTriggerButton((_button) => { 68 quickPick.onDidTriggerButton(async (_button) => {
69 (async () => await makeDebugConfig(ctx, quickPick.activeItems[0].runnable))(); 69 await makeDebugConfig(ctx, quickPick.activeItems[0].runnable);
70 close(); 70 close();
71 }), 71 }),
72 quickPick.onDidChangeActive((active) => { 72 quickPick.onDidChangeActive((active) => {
@@ -128,13 +128,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
128 throw `Unexpected runnable kind: ${runnable.kind}`; 128 throw `Unexpected runnable kind: ${runnable.kind}`;
129 } 129 }
130 130
131 const args = [...runnable.args.cargoArgs]; // should be a copy! 131 const args = createArgs(runnable);
132 if (runnable.args.cargoExtraArgs) {
133 args.push(...runnable.args.cargoExtraArgs); // Append user-specified cargo options.
134 }
135 if (runnable.args.executableArgs.length > 0) {
136 args.push('--', ...runnable.args.executableArgs);
137 }
138 132
139 const definition: tasks.CargoTaskDefinition = { 133 const definition: tasks.CargoTaskDefinition = {
140 type: tasks.TASK_TYPE, 134 type: tasks.TASK_TYPE,
@@ -145,9 +139,21 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
145 overrideCargo: runnable.args.overrideCargo, 139 overrideCargo: runnable.args.overrideCargo,
146 }; 140 };
147 141
142 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
148 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() 143 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
149 const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true); 144 const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true);
150 cargoTask.presentationOptions.clear = true; 145 cargoTask.presentationOptions.clear = true;
151 146
152 return cargoTask; 147 return cargoTask;
153} 148}
149
150export function createArgs(runnable: ra.Runnable): string[] {
151 const args = [...runnable.args.cargoArgs]; // should be a copy!
152 if (runnable.args.cargoExtraArgs) {
153 args.push(...runnable.args.cargoExtraArgs); // Append user-specified cargo options.
154 }
155 if (runnable.args.executableArgs.length > 0) {
156 args.push('--', ...runnable.args.executableArgs);
157 }
158 return args;
159}
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts
index fee736e7d..dc53ebe2e 100644
--- a/editors/code/src/snippets.ts
+++ b/editors/code/src/snippets.ts
@@ -62,7 +62,9 @@ function parseSnippet(snip: string): [string, [number, number]] | undefined {
62 const m = snip.match(/\$(0|\{0:([^}]*)\})/); 62 const m = snip.match(/\$(0|\{0:([^}]*)\})/);
63 if (!m) return undefined; 63 if (!m) return undefined;
64 const placeholder = m[2] ?? ""; 64 const placeholder = m[2] ?? "";
65 const range: [number, number] = [m.index!!, placeholder.length]; 65 if (m.index == null)
66 return undefined;
67 const range: [number, number] = [m.index, placeholder.length];
66 const insert = snip.replace(m[0], placeholder); 68 const insert = snip.replace(m[0], placeholder);
67 return [insert, range]; 69 return [insert, range];
68} 70}