about summary refs log tree commit diff
path: root/src/tools/rust-analyzer/editors
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/editors')
-rw-r--r--src/tools/rust-analyzer/editors/code/README.md28
-rw-r--r--src/tools/rust-analyzer/editors/code/language-configuration.json6
-rw-r--r--src/tools/rust-analyzer/editors/code/package.json37
-rw-r--r--src/tools/rust-analyzer/editors/code/src/bootstrap.ts12
-rw-r--r--src/tools/rust-analyzer/editors/code/src/commands.ts37
-rw-r--r--src/tools/rust-analyzer/editors/code/src/config.ts39
-rw-r--r--src/tools/rust-analyzer/editors/code/src/ctx.ts13
-rw-r--r--src/tools/rust-analyzer/editors/code/src/debug.ts29
-rw-r--r--src/tools/rust-analyzer/editors/code/src/lsp_ext.ts5
-rw-r--r--src/tools/rust-analyzer/editors/code/src/main.ts13
-rw-r--r--src/tools/rust-analyzer/editors/code/src/toolchain.ts7
-rw-r--r--src/tools/rust-analyzer/editors/code/src/util.ts33
12 files changed, 178 insertions, 81 deletions
diff --git a/src/tools/rust-analyzer/editors/code/README.md b/src/tools/rust-analyzer/editors/code/README.md
index 36ab9818822..c02882b4982 100644
--- a/src/tools/rust-analyzer/editors/code/README.md
+++ b/src/tools/rust-analyzer/editors/code/README.md
@@ -15,19 +15,19 @@ It is recommended over and replaces `rust-lang.rust`.
 - apply suggestions from errors
 - ... and many more, check out the [manual] to see them all
 
-[code completion]: https://rust-analyzer.github.io/manual.html#magic-completions
-[imports insertion]: https://rust-analyzer.github.io/manual.html#completion-with-autoimport
-[definition]: https://rust-analyzer.github.io/manual.html#go-to-definition
-[implementation]: https://rust-analyzer.github.io/manual.html#go-to-implementation
-[type definition]: https://rust-analyzer.github.io/manual.html#go-to-type-definition
-[find all references]: https://rust-analyzer.github.io/manual.html#find-all-references
-[workspace symbol search]: https://rust-analyzer.github.io/manual.html#workspace-symbol
-[symbol renaming]: https://rust-analyzer.github.io/manual.html#rename
-[types and documentation on hover]: https://rust-analyzer.github.io/manual.html#hover
-[inlay hints]: https://rust-analyzer.github.io/manual.html#inlay-hints
-[semantic syntax highlighting]: https://rust-analyzer.github.io/manual.html#semantic-syntax-highlighting
-[assists (code actions)]: https://rust-analyzer.github.io/manual.html#assists-code-actions
-[manual]: https://rust-analyzer.github.io/manual.html
+[code completion]: https://rust-analyzer.github.io/book/features.html#magic-completions
+[imports insertion]: https://rust-analyzer.github.io/book/features.html#completion-with-autoimport
+[definition]: https://rust-analyzer.github.io/book/features.html#go-to-definition
+[implementation]: https://rust-analyzer.github.io/book/features.html#go-to-implementation
+[type definition]: https://rust-analyzer.github.io/book/features.html#go-to-type-definition
+[find all references]: https://rust-analyzer.github.io/book/features.html#find-all-references
+[workspace symbol search]: https://rust-analyzer.github.io/book/features.html#workspace-symbol
+[symbol renaming]: https://rust-analyzer.github.io/book/features.html#rename
+[types and documentation on hover]: https://rust-analyzer.github.io/book/features.html#hover
+[inlay hints]: https://rust-analyzer.github.io/book/features.html#inlay-hints
+[semantic syntax highlighting]: https://rust-analyzer.github.io/book/features.html#semantic-syntax-highlighting
+[assists (code actions)]: https://rust-analyzer.github.io/book/assists.html
+[manual]: https://rust-analyzer.github.io/book/features.html
 
 ## Quick start
 
@@ -41,7 +41,7 @@ It is recommended over and replaces `rust-lang.rust`.
 
 This extension provides configurations through VSCode's configuration settings. All configurations are under `rust-analyzer.*`.
 
-See [the manual](https://rust-analyzer.github.io/manual.html#vs-code-2) for more information on VSCode specific configurations.
+See [the manual](https://rust-analyzer.github.io/book/editor_features.html#vs-code) for more information on VSCode specific configurations.
 
 ## Communication
 
diff --git a/src/tools/rust-analyzer/editors/code/language-configuration.json b/src/tools/rust-analyzer/editors/code/language-configuration.json
index 6619d0c85c5..aacd48b0f83 100644
--- a/src/tools/rust-analyzer/editors/code/language-configuration.json
+++ b/src/tools/rust-analyzer/editors/code/language-configuration.json
@@ -14,9 +14,9 @@
         ["(", ")"]
     ],
     "autoClosingPairs": [
-        { "open": "{", "close": "}" },
-        { "open": "[", "close": "]" },
-        { "open": "(", "close": ")" },
+        { "open": "{", "close": "}", "notIn": ["string"] },
+        { "open": "[", "close": "]", "notIn": ["string"] },
+        { "open": "(", "close": ")", "notIn": ["string"] },
         { "open": "\"", "close": "\"", "notIn": ["string"] },
         { "open": "/*", "close": " */", "notIn": ["string"] },
         { "open": "`", "close": "`", "notIn": ["string"] },
diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json
index 9df41c7487c..a282eea9997 100644
--- a/src/tools/rust-analyzer/editors/code/package.json
+++ b/src/tools/rust-analyzer/editors/code/package.json
@@ -171,6 +171,11 @@
                 "category": "rust-analyzer"
             },
             {
+                "command": "rust-analyzer.childModules",
+                "title": "Locate child modules",
+                "category": "rust-analyzer"
+            },
+            {
                 "command": "rust-analyzer.joinLines",
                 "title": "Join lines",
                 "category": "rust-analyzer"
@@ -538,7 +543,8 @@
                         "additionalProperties": {
                             "type": [
                                 "string",
-                                "number"
+                                "number",
+                                "null"
                             ]
                         },
                         "default": null,
@@ -606,11 +612,6 @@
                             "/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"
                         }
                     },
-                    "rust-analyzer.debug.openDebugPane": {
-                        "markdownDescription": "Whether to open up the `Debug Panel` on debugging start.",
-                        "type": "boolean",
-                        "default": false
-                    },
                     "rust-analyzer.debug.buildBeforeRestart": {
                         "markdownDescription": "Whether to rebuild the project modules before debugging the same test again",
                         "type": "boolean",
@@ -899,6 +900,16 @@
             {
                 "title": "cargo",
                 "properties": {
+                    "rust-analyzer.cargo.noDeps": {
+                        "markdownDescription": "Whether to skip fetching dependencies. If set to \"true\", the analysis is performed\nentirely offline, and Cargo metadata for dependencies is not fetched.",
+                        "default": false,
+                        "type": "boolean"
+                    }
+                }
+            },
+            {
+                "title": "cargo",
+                "properties": {
                     "rust-analyzer.cargo.sysroot": {
                         "markdownDescription": "Relative path to the sysroot, or \"discover\" to try to automatically find it via\n\"rustc --print sysroot\".\n\nUnsetting this disables sysroot loading.\n\nThis option does not take effect until rust-analyzer is restarted.",
                         "default": "discover",
@@ -1875,7 +1886,7 @@
                 "title": "imports",
                 "properties": {
                     "rust-analyzer.imports.group.enable": {
-                        "markdownDescription": "Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.",
+                        "markdownDescription": "Group inserted imports by the [following order](https://rust-analyzer.github.io/book/features.html#auto-import). Groups are separated by newlines.",
                         "default": true,
                         "type": "boolean"
                     }
@@ -2823,7 +2834,7 @@
                 "title": "workspace",
                 "properties": {
                     "rust-analyzer.workspace.discoverConfig": {
-                        "markdownDescription": "Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].\n\n[`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.\n`progress_label` is used for the title in progress indicators, whereas `files_to_watch`\nis used to determine which build system-specific files should be watched in order to\nreload rust-analyzer.\n\nBelow is an example of a valid configuration:\n```json\n\"rust-analyzer.workspace.discoverConfig\": {\n        \"command\": [\n                \"rust-project\",\n                \"develop-json\"\n        ],\n        \"progressLabel\": \"rust-analyzer\",\n        \"filesToWatch\": [\n                \"BUCK\"\n        ]\n}\n```\n\n## On `DiscoverWorkspaceConfig::command`\n\n**Warning**: This format is provisional and subject to change.\n\n[`DiscoverWorkspaceConfig::command`] *must* return a JSON object\ncorresponding to `DiscoverProjectData::Finished`:\n\n```norun\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"kind\")]\n#[serde(rename_all = \"snake_case\")]\nenum DiscoverProjectData {\n        Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },\n        Error { error: String, source: Option<String> },\n        Progress { message: String },\n}\n```\n\nAs JSON, `DiscoverProjectData::Finished` is:\n\n```json\n{\n        // the internally-tagged representation of the enum.\n        \"kind\": \"finished\",\n        // the file used by a non-Cargo build system to define\n        // a package or target.\n        \"buildfile\": \"rust-analyzer/BUILD\",\n        // the contents of a rust-project.json, elided for brevity\n        \"project\": {\n                \"sysroot\": \"foo\",\n                \"crates\": []\n        }\n}\n```\n\nIt is encouraged, but not required, to use the other variants on\n`DiscoverProjectData` to provide a more polished end-user experience.\n\n`DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`,\nwhich will be substituted with the JSON-serialized form of the following\nenum:\n\n```norun\n#[derive(PartialEq, Clone, Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DiscoverArgument {\n     Path(AbsPathBuf),\n     Buildfile(AbsPathBuf),\n}\n```\n\nThe JSON representation of `DiscoverArgument::Path` is:\n\n```json\n{\n        \"path\": \"src/main.rs\"\n}\n```\n\nSimilarly, the JSON representation of `DiscoverArgument::Buildfile` is:\n\n```\n{\n        \"buildfile\": \"BUILD\"\n}\n```\n\n`DiscoverArgument::Path` is used to find and generate a `rust-project.json`,\nand therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to\nto update an existing workspace. As a reference for implementors,\nbuck2's `rust-project` will likely be useful:\nhttps://github.com/facebook/buck2/tree/main/integrations/rust-project.",
+                        "markdownDescription": "Enables automatic discovery of projects using [`DiscoverWorkspaceConfig::command`].\n\n[`DiscoverWorkspaceConfig`] also requires setting `progress_label` and `files_to_watch`.\n`progress_label` is used for the title in progress indicators, whereas `files_to_watch`\nis used to determine which build system-specific files should be watched in order to\nreload rust-analyzer.\n\nBelow is an example of a valid configuration:\n```json\n\"rust-analyzer.workspace.discoverConfig\": {\n        \"command\": [\n                \"rust-project\",\n                \"develop-json\"\n        ],\n        \"progressLabel\": \"rust-analyzer\",\n        \"filesToWatch\": [\n                \"BUCK\"\n        ]\n}\n```\n\n## On `DiscoverWorkspaceConfig::command`\n\n**Warning**: This format is provisional and subject to change.\n\n[`DiscoverWorkspaceConfig::command`] *must* return a JSON object\ncorresponding to `DiscoverProjectData::Finished`:\n\n```norun\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"kind\")]\n#[serde(rename_all = \"snake_case\")]\nenum DiscoverProjectData {\n        Finished { buildfile: Utf8PathBuf, project: ProjectJsonData },\n        Error { error: String, source: Option<String> },\n        Progress { message: String },\n}\n```\n\nAs JSON, `DiscoverProjectData::Finished` is:\n\n```json\n{\n        // the internally-tagged representation of the enum.\n        \"kind\": \"finished\",\n        // the file used by a non-Cargo build system to define\n        // a package or target.\n        \"buildfile\": \"rust-analyzer/BUILD\",\n        // the contents of a rust-project.json, elided for brevity\n        \"project\": {\n                \"sysroot\": \"foo\",\n                \"crates\": []\n        }\n}\n```\n\nIt is encouraged, but not required, to use the other variants on\n`DiscoverProjectData` to provide a more polished end-user experience.\n\n`DiscoverWorkspaceConfig::command` may *optionally* include an `{arg}`,\nwhich will be substituted with the JSON-serialized form of the following\nenum:\n\n```norun\n#[derive(PartialEq, Clone, Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DiscoverArgument {\n     Path(AbsPathBuf),\n     Buildfile(AbsPathBuf),\n}\n```\n\nThe JSON representation of `DiscoverArgument::Path` is:\n\n```json\n{\n        \"path\": \"src/main.rs\"\n}\n```\n\nSimilarly, the JSON representation of `DiscoverArgument::Buildfile` is:\n\n```json\n{\n        \"buildfile\": \"BUILD\"\n}\n```\n\n`DiscoverArgument::Path` is used to find and generate a `rust-project.json`,\nand therefore, a workspace, whereas `DiscoverArgument::buildfile` is used to\nto update an existing workspace. As a reference for implementors,\nbuck2's `rust-project` will likely be useful:\nhttps://github.com/facebook/buck2/tree/main/integrations/rust-project.",
                         "default": null,
                         "anyOf": [
                             {
@@ -3369,6 +3380,10 @@
                     "when": "inRustProject"
                 },
                 {
+                    "command": "rust-analyzer.childModules",
+                    "when": "inRustProject"
+                },
+                {
                     "command": "rust-analyzer.joinLines",
                     "when": "inRustProject"
                 },
@@ -3488,6 +3503,7 @@
         "views": {
             "explorer": [
                 {
+                    "icon": "$(package)",
                     "id": "rustDependencies",
                     "name": "Rust Dependencies",
                     "when": "inRustProject && config.rust-analyzer.showDependenciesExplorer"
@@ -3495,6 +3511,7 @@
             ],
             "rustSyntaxTreeContainer": [
                 {
+                    "icon": "$(file-code)",
                     "id": "rustSyntaxTree",
                     "name": "Rust Syntax Tree",
                     "when": "inRustProject && config.rust-analyzer.showSyntaxTree"
@@ -3537,13 +3554,13 @@
                     {
                         "id": "docs",
                         "title": "Visit the docs!",
-                        "description": "Confused about configurations? Want to learn more about rust-analyzer? Visit the [User Manual](https://rust-analyzer.github.io/manual.html)!",
+                        "description": "Confused about configurations? Want to learn more about rust-analyzer? Visit the [User Manual](https://rust-analyzer.github.io/book/)!",
                         "media": {
                             "image": "./icon.png",
                             "altText": "rust-analyzer logo"
                         },
                         "completionEvents": [
-                            "onLink:https://rust-analyzer.github.io/manual.html"
+                            "onLink:https://rust-analyzer.github.io/book/"
                         ]
                     },
                     {
diff --git a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts
index bccae73c9a7..bddf195803d 100644
--- a/src/tools/rust-analyzer/editors/code/src/bootstrap.ts
+++ b/src/tools/rust-analyzer/editors/code/src/bootstrap.ts
@@ -15,7 +15,7 @@ export async function bootstrap(
     if (!path) {
         throw new Error(
             "rust-analyzer Language Server is not available. " +
-                "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation).",
+                "Please, ensure its [proper installation](https://rust-analyzer.github.io/book/installation.html).",
         );
     }
 
@@ -187,8 +187,16 @@ async function hasToolchainFileWithRaDeclared(uri: vscode.Uri): Promise<boolean>
 export async function isValidExecutable(path: string, extraEnv: Env): Promise<boolean> {
     log.debug("Checking availability of a binary at", path);
 
+    const newEnv = { ...process.env };
+    for (const [k, v] of Object.entries(extraEnv)) {
+        if (v) {
+            newEnv[k] = v;
+        } else if (k in newEnv) {
+            delete newEnv[k];
+        }
+    }
     const res = await spawnAsync(path, ["--version"], {
-        env: { ...process.env, ...extraEnv },
+        env: newEnv,
     });
 
     if (res.error) {
diff --git a/src/tools/rust-analyzer/editors/code/src/commands.ts b/src/tools/rust-analyzer/editors/code/src/commands.ts
index 4e614d32057..3ac1a933d9e 100644
--- a/src/tools/rust-analyzer/editors/code/src/commands.ts
+++ b/src/tools/rust-analyzer/editors/code/src/commands.ts
@@ -266,6 +266,43 @@ export function parentModule(ctx: CtxInit): Cmd {
     };
 }
 
+export function childModules(ctx: CtxInit): Cmd {
+    return async () => {
+        const editor = vscode.window.activeTextEditor;
+        if (!editor) return;
+        if (!(isRustDocument(editor.document) || isCargoTomlDocument(editor.document))) return;
+
+        const client = ctx.client;
+
+        const locations = await client.sendRequest(ra.childModules, {
+            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
+            position: client.code2ProtocolConverter.asPosition(editor.selection.active),
+        });
+        if (!locations) return;
+
+        if (locations.length === 1) {
+            const loc = unwrapUndefinable(locations[0]);
+
+            const uri = client.protocol2CodeConverter.asUri(loc.targetUri);
+            const range = client.protocol2CodeConverter.asRange(loc.targetRange);
+
+            const doc = await vscode.workspace.openTextDocument(uri);
+            const e = await vscode.window.showTextDocument(doc);
+            e.selection = new vscode.Selection(range.start, range.start);
+            e.revealRange(range, vscode.TextEditorRevealType.InCenter);
+        } else {
+            const uri = editor.document.uri.toString();
+            const position = client.code2ProtocolConverter.asPosition(editor.selection.active);
+            await showReferencesImpl(
+                client,
+                uri,
+                position,
+                locations.map((loc) => lc.Location.create(loc.targetUri, loc.targetRange)),
+            );
+        }
+    };
+}
+
 export function openCargoToml(ctx: CtxInit): Cmd {
     return async () => {
         const editor = ctx.activeRustEditor;
diff --git a/src/tools/rust-analyzer/editors/code/src/config.ts b/src/tools/rust-analyzer/editors/code/src/config.ts
index 896b3c10cbf..f36e18a73da 100644
--- a/src/tools/rust-analyzer/editors/code/src/config.ts
+++ b/src/tools/rust-analyzer/editors/code/src/config.ts
@@ -2,7 +2,7 @@ import * as Is from "vscode-languageclient/lib/common/utils/is";
 import * as os from "os";
 import * as path from "path";
 import * as vscode from "vscode";
-import { expectNotUndefined, log, unwrapUndefinable } from "./util";
+import { expectNotUndefined, log, normalizeDriveLetter, unwrapUndefinable } from "./util";
 import type { Env } from "./util";
 import type { Disposable } from "vscode";
 
@@ -213,12 +213,13 @@ export class Config {
 
     get serverExtraEnv(): Env {
         const extraEnv =
-            this.get<{ [key: string]: string | number } | null>("server.extraEnv") ?? {};
+            this.get<{ [key: string]: { toString(): string } | null } | null>("server.extraEnv") ??
+            {};
         return substituteVariablesInEnv(
             Object.fromEntries(
                 Object.entries(extraEnv).map(([k, v]) => [
                     k,
-                    typeof v !== "string" ? v.toString() : v,
+                    typeof v === "string" ? v : v?.toString(),
                 ]),
             ),
         );
@@ -323,7 +324,6 @@ export class Config {
         return {
             engine: this.get<string>("debug.engine"),
             engineSettings: this.get<object>("debug.engineSettings") ?? {},
-            openDebugPane: this.get<boolean>("debug.openDebugPane"),
             buildBeforeRestart: this.get<boolean>("debug.buildBeforeRestart"),
             sourceFileMap: sourceFileMap,
         };
@@ -399,6 +399,7 @@ export function prepareVSCodeConfig<T>(resp: T): T {
 
 // FIXME: Merge this with `substituteVSCodeVariables` above
 export function substituteVariablesInEnv(env: Env): Env {
+    const depRe = new RegExp(/\${(?<depName>.+?)}/g);
     const missingDeps = new Set<string>();
     // vscode uses `env:ENV_NAME` for env vars resolution, and it's easier
     // to follow the same convention for our dependency tracking
@@ -406,15 +407,16 @@ export function substituteVariablesInEnv(env: Env): Env {
     const envWithDeps = Object.fromEntries(
         Object.entries(env).map(([key, value]) => {
             const deps = new Set<string>();
-            const depRe = new RegExp(/\${(?<depName>.+?)}/g);
-            let match = undefined;
-            while ((match = depRe.exec(value))) {
-                const depName = unwrapUndefinable(match.groups?.["depName"]);
-                deps.add(depName);
-                // `depName` at this point can have a form of `expression` or
-                // `prefix:expression`
-                if (!definedEnvKeys.has(depName)) {
-                    missingDeps.add(depName);
+            if (value) {
+                let match = undefined;
+                while ((match = depRe.exec(value))) {
+                    const depName = unwrapUndefinable(match.groups?.["depName"]);
+                    deps.add(depName);
+                    // `depName` at this point can have a form of `expression` or
+                    // `prefix:expression`
+                    if (!definedEnvKeys.has(depName)) {
+                        missingDeps.add(depName);
+                    }
                 }
             }
             return [`env:${key}`, { deps: [...deps], value }];
@@ -455,11 +457,10 @@ export function substituteVariablesInEnv(env: Env): Env {
     do {
         leftToResolveSize = toResolve.size;
         for (const key of toResolve) {
-            const item = unwrapUndefinable(envWithDeps[key]);
-            if (item.deps.every((dep) => resolved.has(dep))) {
-                item.value = item.value.replace(/\${(?<depName>.+?)}/g, (_wholeMatch, depName) => {
-                    const item = unwrapUndefinable(envWithDeps[depName]);
-                    return item.value;
+            const item = envWithDeps[key];
+            if (item && item.deps.every((dep) => resolved.has(dep))) {
+                item.value = item.value?.replace(/\${(?<depName>.+?)}/g, (_wholeMatch, depName) => {
+                    return envWithDeps[depName]?.value ?? "";
                 });
                 resolved.add(key);
                 toResolve.delete(key);
@@ -499,7 +500,7 @@ function computeVscodeVar(varName: string): string | null {
                   // user has opened on Editor startup. Could lead to
                   // unpredictable workspace selection in practice.
                   // It's better to pick the first one
-                  folder.uri.fsPath;
+                  normalizeDriveLetter(folder.uri.fsPath);
         return fsPath;
     };
     // https://code.visualstudio.com/docs/editor/variables-reference
diff --git a/src/tools/rust-analyzer/editors/code/src/ctx.ts b/src/tools/rust-analyzer/editors/code/src/ctx.ts
index 37a2ee23691..e55754fb9f0 100644
--- a/src/tools/rust-analyzer/editors/code/src/ctx.ts
+++ b/src/tools/rust-analyzer/editors/code/src/ctx.ts
@@ -190,11 +190,11 @@ export class Ctx implements RustAnalyzerExtensionApi {
         }
 
         if (!this.traceOutputChannel) {
-            this.traceOutputChannel = new LazyOutputChannel("Rust Analyzer Language Server Trace");
+            this.traceOutputChannel = new LazyOutputChannel("rust-analyzer LSP Trace");
             this.pushExtCleanup(this.traceOutputChannel);
         }
         if (!this.outputChannel) {
-            this.outputChannel = vscode.window.createOutputChannel("Rust Analyzer Language Server");
+            this.outputChannel = vscode.window.createOutputChannel("rust-analyzer Language Server");
             this.pushExtCleanup(this.outputChannel);
         }
 
@@ -213,7 +213,14 @@ export class Ctx implements RustAnalyzerExtensionApi {
                     this.refreshServerStatus();
                 },
             );
-            const newEnv = Object.assign({}, process.env, this.config.serverExtraEnv);
+            const newEnv = { ...process.env };
+            for (const [k, v] of Object.entries(this.config.serverExtraEnv)) {
+                if (v) {
+                    newEnv[k] = v;
+                } else if (k in newEnv) {
+                    delete newEnv[k];
+                }
+            }
             const run: lc.Executable = {
                 command: this._serverPath,
                 options: { env: newEnv },
diff --git a/src/tools/rust-analyzer/editors/code/src/debug.ts b/src/tools/rust-analyzer/editors/code/src/debug.ts
index 72a9aabc043..adb75c23c70 100644
--- a/src/tools/rust-analyzer/editors/code/src/debug.ts
+++ b/src/tools/rust-analyzer/editors/code/src/debug.ts
@@ -6,11 +6,9 @@ import type * as ra from "./lsp_ext";
 import { Cargo } from "./toolchain";
 import type { Ctx } from "./ctx";
 import { createTaskFromRunnable, prepareEnv } from "./run";
-import { execute, isCargoRunnableArgs, unwrapUndefinable } from "./util";
+import { execute, isCargoRunnableArgs, unwrapUndefinable, log, normalizeDriveLetter } from "./util";
 import type { Config } from "./config";
 
-const debugOutput = vscode.window.createOutputChannel("Debug");
-
 // Here we want to keep track on everything that's currently running
 const activeDebugSessionIds: string[] = [];
 
@@ -56,15 +54,14 @@ export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promis
     if (-1 !== index) {
         debugConfig = configurations[index];
         message = " (from launch.json)";
-        debugOutput.clear();
     } else {
         debugConfig = await getDebugConfiguration(ctx.config, runnable);
     }
 
     if (!debugConfig) return false;
 
-    debugOutput.appendLine(`Launching debug configuration${message}:`);
-    debugOutput.appendLine(JSON.stringify(debugConfig, null, 2));
+    log.debug(`Launching debug configuration${message}:`);
+    log.debug(JSON.stringify(debugConfig, null, 2));
     return vscode.debug.startDebugging(undefined, debugConfig);
 }
 
@@ -118,10 +115,6 @@ async function getDebugConfiguration(
         return;
     }
 
-    debugOutput.clear();
-    if (config.debug.openDebugPane) {
-        debugOutput.show(true);
-    }
     // folder exists or RA is not active.
 
     const workspaceFolders = vscode.workspace.workspaceFolders!;
@@ -134,20 +127,14 @@ async function getDebugConfiguration(
               firstWorkspace;
 
     const workspace = unwrapUndefinable(maybeWorkspace);
-    let wsFolder = path.normalize(workspace.uri.fsPath);
-    if (os.platform() === "win32") {
-        // in windows, the drive letter can vary in casing for VSCode, so we gotta normalize that first
-        wsFolder = wsFolder.replace(/^[a-z]:\\/, (c) => c.toUpperCase());
-    }
+    const wsFolder = normalizeDriveLetter(path.normalize(workspace.uri.fsPath));
 
     const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : "";
     function simplifyPath(p: string): string {
         // in windows, the drive letter can vary in casing for VSCode, so we gotta normalize that first
-        if (os.platform() === "win32") {
-            p = p.replace(/^[a-z]:\\/, (c) => c.toUpperCase());
-        }
+        p = normalizeDriveLetter(path.normalize(p));
         // see https://github.com/rust-lang/rust-analyzer/pull/5513#issuecomment-663458818 for why this is needed
-        return path.normalize(p).replace(wsFolder, `\${workspaceFolder${workspaceQualifier}}`);
+        return p.replace(wsFolder, `\${workspaceFolder${workspaceQualifier}}`);
     }
 
     const executable = await getDebugExecutable(
@@ -232,7 +219,7 @@ async function discoverSourceFileMap(
         const commitHash = rx.exec(data)?.[1];
         if (commitHash) {
             const rustlib = path.normalize(sysroot + "/lib/rustlib/src/rust");
-            return { source: rustlib, destination: rustlib };
+            return { source: "/rustc/" + commitHash, destination: rustlib };
         }
     }
 
@@ -321,7 +308,7 @@ async function getDebugExecutable(
     runnableArgs: ra.CargoRunnableArgs,
     env: Record<string, string>,
 ): Promise<string> {
-    const cargo = new Cargo(runnableArgs.workspaceRoot || ".", debugOutput, env);
+    const cargo = new Cargo(runnableArgs.workspaceRoot || ".", env);
     const executable = await cargo.executableFromArgs(runnableArgs);
 
     // if we are here, there were no compilation errors.
diff --git a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts
index af5129ac963..20952e93ccc 100644
--- a/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts
+++ b/src/tools/rust-analyzer/editors/code/src/lsp_ext.ts
@@ -194,6 +194,11 @@ export const parentModule = new lc.RequestType<
     lc.LocationLink[] | null,
     void
 >("experimental/parentModule");
+export const childModules = new lc.RequestType<
+    lc.TextDocumentPositionParams,
+    lc.LocationLink[] | null,
+    void
+>("experimental/childModules");
 export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>(
     "experimental/runnables",
 );
diff --git a/src/tools/rust-analyzer/editors/code/src/main.ts b/src/tools/rust-analyzer/editors/code/src/main.ts
index 451294e26f6..5e500730693 100644
--- a/src/tools/rust-analyzer/editors/code/src/main.ts
+++ b/src/tools/rust-analyzer/editors/code/src/main.ts
@@ -158,6 +158,7 @@ function createCommands(): Record<string, CommandFactory> {
         matchingBrace: { enabled: commands.matchingBrace },
         joinLines: { enabled: commands.joinLines },
         parentModule: { enabled: commands.parentModule },
+        childModules: { enabled: commands.childModules },
         viewHir: { enabled: commands.viewHir },
         viewMir: { enabled: commands.viewMir },
         interpretFunction: { enabled: commands.interpretFunction },
@@ -187,7 +188,9 @@ function createCommands(): Record<string, CommandFactory> {
         openWalkthrough: { enabled: commands.openWalkthrough },
         // Internal commands which are invoked by the server.
         applyActionGroup: { enabled: commands.applyActionGroup },
-        applySnippetWorkspaceEdit: { enabled: commands.applySnippetWorkspaceEditCommand },
+        applySnippetWorkspaceEdit: {
+            enabled: commands.applySnippetWorkspaceEditCommand,
+        },
         debugSingle: { enabled: commands.debugSingle },
         gotoLocation: { enabled: commands.gotoLocation },
         hoverRefCommandProxy: { enabled: commands.hoverRefCommandProxy },
@@ -200,8 +203,12 @@ function createCommands(): Record<string, CommandFactory> {
         revealDependency: { enabled: commands.revealDependency },
         syntaxTreeReveal: { enabled: commands.syntaxTreeReveal },
         syntaxTreeCopy: { enabled: commands.syntaxTreeCopy },
-        syntaxTreeHideWhitespace: { enabled: commands.syntaxTreeHideWhitespace },
-        syntaxTreeShowWhitespace: { enabled: commands.syntaxTreeShowWhitespace },
+        syntaxTreeHideWhitespace: {
+            enabled: commands.syntaxTreeHideWhitespace,
+        },
+        syntaxTreeShowWhitespace: {
+            enabled: commands.syntaxTreeShowWhitespace,
+        },
     };
 }
 
diff --git a/src/tools/rust-analyzer/editors/code/src/toolchain.ts b/src/tools/rust-analyzer/editors/code/src/toolchain.ts
index bb061442953..a859ce6ff00 100644
--- a/src/tools/rust-analyzer/editors/code/src/toolchain.ts
+++ b/src/tools/rust-analyzer/editors/code/src/toolchain.ts
@@ -37,7 +37,6 @@ interface CompilerMessage {
 export class Cargo {
     constructor(
         readonly rootFolder: string,
-        readonly output: vscode.OutputChannel,
         readonly env: Record<string, string>,
     ) {}
 
@@ -93,14 +92,14 @@ export class Cargo {
                             });
                         }
                     } else if (message.reason === "compiler-message") {
-                        this.output.append(message.message.rendered);
+                        log.info(message.message.rendered);
                     }
                 },
-                (stderr) => this.output.append(stderr),
+                (stderr) => log.error(stderr),
                 env,
             );
         } catch (err) {
-            this.output.show(true);
+            log.error(`Cargo invocation has failed: ${err}`);
             throw new Error(`Cargo invocation has failed: ${err}`);
         }
 
diff --git a/src/tools/rust-analyzer/editors/code/src/util.ts b/src/tools/rust-analyzer/editors/code/src/util.ts
index 93c7bf8d73e..410b055100a 100644
--- a/src/tools/rust-analyzer/editors/code/src/util.ts
+++ b/src/tools/rust-analyzer/editors/code/src/util.ts
@@ -14,11 +14,11 @@ export function assert(condition: boolean, explanation: string): asserts conditi
 }
 
 export type Env = {
-    [name: string]: string;
+    [name: string]: string | undefined;
 };
 
 class Log {
-    private readonly output = vscode.window.createOutputChannel("Rust Analyzer Client", {
+    private readonly output = vscode.window.createOutputChannel("rust-analyzer Extension", {
         log: true,
     });
 
@@ -299,3 +299,32 @@ export async function spawnAsync(
         };
     }
 }
+
+export const isWindows = process.platform === "win32";
+
+export function isWindowsDriveLetter(code: number): boolean {
+    // Copied from https://github.com/microsoft/vscode/blob/02c2dba5f2669b924fd290dff7d2ff3460791996/src/vs/base/common/extpath.ts#L265-L267
+    return (
+        (code >= /* CharCode.A */ 65 && code <= /* CharCode.Z */ 90) ||
+        (code >= /* CharCode.a */ 97 && code <= /* CharCode.z */ 122)
+    );
+}
+export function hasDriveLetter(path: string, isWindowsOS: boolean = isWindows): boolean {
+    // Copied from https://github.com/microsoft/vscode/blob/02c2dba5f2669b924fd290dff7d2ff3460791996/src/vs/base/common/extpath.ts#L324-L330
+    if (isWindowsOS) {
+        return (
+            isWindowsDriveLetter(path.charCodeAt(0)) &&
+            path.charCodeAt(1) === /* CharCode.Colon */ 58
+        );
+    }
+
+    return false;
+}
+export function normalizeDriveLetter(path: string, isWindowsOS: boolean = isWindows): string {
+    // Copied from https://github.com/microsoft/vscode/blob/02c2dba5f2669b924fd290dff7d2ff3460791996/src/vs/base/common/labels.ts#L140-L146
+    if (hasDriveLetter(path, isWindowsOS)) {
+        return path.charAt(0).toUpperCase() + path.slice(1);
+    }
+
+    return path;
+}