about summary refs log tree commit diff
path: root/editors/code/src
diff options
context:
space:
mode:
authorLaurențiu Nicola <lnicola@dend.ro>2023-07-17 16:09:39 +0300
committerLaurențiu Nicola <lnicola@dend.ro>2023-07-17 16:09:39 +0300
commit71499fcd2273ba05d2c6173591bf66113fa19a5f (patch)
treee5d7d3ad656222feb1b50014b7dc4e0de964b5a6 /editors/code/src
parent6502421771dabc8a3b97c06b348fd9e04270e4f3 (diff)
parentd82451103962b1482cb137850c81a1acb34db0e7 (diff)
downloadrust-71499fcd2273ba05d2c6173591bf66113fa19a5f.tar.gz
rust-71499fcd2273ba05d2c6173591bf66113fa19a5f.zip
Merge remote-tracking branch 'upstream/master' into sync-from-rust
Diffstat (limited to 'editors/code/src')
-rw-r--r--editors/code/src/ast_inspector.ts26
-rw-r--r--editors/code/src/bootstrap.ts22
-rw-r--r--editors/code/src/client.ts76
-rw-r--r--editors/code/src/commands.ts386
-rw-r--r--editors/code/src/config.ts81
-rw-r--r--editors/code/src/ctx.ts48
-rw-r--r--editors/code/src/debug.ts37
-rw-r--r--editors/code/src/dependencies_provider.ts28
-rw-r--r--editors/code/src/diagnostics.ts20
-rw-r--r--editors/code/src/lsp_ext.ts55
-rw-r--r--editors/code/src/main.ts23
-rw-r--r--editors/code/src/nullable.ts19
-rw-r--r--editors/code/src/persistent_state.ts2
-rw-r--r--editors/code/src/run.ts28
-rw-r--r--editors/code/src/snippets.ts16
-rw-r--r--editors/code/src/tasks.ts17
-rw-r--r--editors/code/src/toolchain.ts19
-rw-r--r--editors/code/src/undefinable.ts19
-rw-r--r--editors/code/src/util.ts36
19 files changed, 659 insertions, 299 deletions
diff --git a/editors/code/src/ast_inspector.ts b/editors/code/src/ast_inspector.ts
index 176040120f4..688c53a9b1f 100644
--- a/editors/code/src/ast_inspector.ts
+++ b/editors/code/src/ast_inspector.ts
@@ -1,7 +1,8 @@
 import * as vscode from "vscode";
 
-import { Ctx, Disposable } from "./ctx";
-import { RustEditor, isRustEditor } from "./util";
+import type { Ctx, Disposable } from "./ctx";
+import { type RustEditor, isRustEditor } from "./util";
+import { unwrapUndefinable } from "./undefinable";
 
 // FIXME: consider implementing this via the Tree View API?
 // https://code.visualstudio.com/api/extension-guides/tree-view
@@ -36,23 +37,23 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
 
     constructor(ctx: Ctx) {
         ctx.pushExtCleanup(
-            vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this)
+            vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this),
         );
         ctx.pushExtCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this));
         vscode.workspace.onDidCloseTextDocument(
             this.onDidCloseTextDocument,
             this,
-            ctx.subscriptions
+            ctx.subscriptions,
         );
         vscode.workspace.onDidChangeTextDocument(
             this.onDidChangeTextDocument,
             this,
-            ctx.subscriptions
+            ctx.subscriptions,
         );
         vscode.window.onDidChangeVisibleTextEditors(
             this.onDidChangeVisibleTextEditors,
             this,
-            ctx.subscriptions
+            ctx.subscriptions,
         );
     }
     dispose() {
@@ -84,7 +85,7 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
 
     private findAstTextEditor(): undefined | vscode.TextEditor {
         return vscode.window.visibleTextEditors.find(
-            (it) => it.document.uri.scheme === "rust-analyzer"
+            (it) => it.document.uri.scheme === "rust-analyzer",
         );
     }
 
@@ -99,7 +100,7 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
     // additional positional params are omitted
     provideDefinition(
         doc: vscode.TextDocument,
-        pos: vscode.Position
+        pos: vscode.Position,
     ): vscode.ProviderResult<vscode.DefinitionLink[]> {
         if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) {
             return;
@@ -131,7 +132,7 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
     // additional positional params are omitted
     provideHover(
         doc: vscode.TextDocument,
-        hoverPosition: vscode.Position
+        hoverPosition: vscode.Position,
     ): vscode.ProviderResult<vscode.Hover> {
         if (!this.rustEditor) return;
 
@@ -158,14 +159,15 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
 
     private parseRustTextRange(
         doc: vscode.TextDocument,
-        astLine: string
+        astLine: string,
     ): undefined | vscode.Range {
         const parsedRange = /(\d+)\.\.(\d+)/.exec(astLine);
         if (!parsedRange) return;
 
         const [begin, end] = parsedRange.slice(1).map((off) => this.positionAt(doc, +off));
-
-        return new vscode.Range(begin, end);
+        const actualBegin = unwrapUndefinable(begin);
+        const actualEnd = unwrapUndefinable(end);
+        return new vscode.Range(actualBegin, actualEnd);
     }
 
     // Memoize the last value, otherwise the CPU is at 100% single core
diff --git a/editors/code/src/bootstrap.ts b/editors/code/src/bootstrap.ts
index b38fa06a85c..ef4dff095cf 100644
--- a/editors/code/src/bootstrap.ts
+++ b/editors/code/src/bootstrap.ts
@@ -1,20 +1,20 @@
 import * as vscode from "vscode";
 import * as os from "os";
-import { Config } from "./config";
+import type { Config } from "./config";
 import { log, isValidExecutable } from "./util";
-import { PersistentState } from "./persistent_state";
+import type { PersistentState } from "./persistent_state";
 import { exec } from "child_process";
 
 export async function bootstrap(
     context: vscode.ExtensionContext,
     config: Config,
-    state: PersistentState
+    state: PersistentState,
 ): Promise<string> {
     const path = await getServer(context, config, state);
     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/manual.html#installation).",
         );
     }
 
@@ -34,9 +34,9 @@ export async function bootstrap(
 async function getServer(
     context: vscode.ExtensionContext,
     config: Config,
-    state: PersistentState
+    state: PersistentState,
 ): Promise<string | undefined> {
-    const explicitPath = process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath;
+    const explicitPath = process.env["__RA_LSP_SERVER_DEBUG"] ?? config.serverPath;
     if (explicitPath) {
         if (explicitPath.startsWith("~/")) {
             return os.homedir() + explicitPath.slice("~".length);
@@ -49,7 +49,7 @@ async function getServer(
     const bundled = vscode.Uri.joinPath(context.extensionUri, "server", `rust-analyzer${ext}`);
     const bundledExists = await vscode.workspace.fs.stat(bundled).then(
         () => true,
-        () => false
+        () => false,
     );
     if (bundledExists) {
         let server = bundled;
@@ -58,7 +58,7 @@ async function getServer(
             const dest = vscode.Uri.joinPath(config.globalStorageUri, `rust-analyzer${ext}`);
             let exists = await vscode.workspace.fs.stat(dest).then(
                 () => true,
-                () => false
+                () => false,
             );
             if (exists && config.package.version !== state.serverVersion) {
                 await vscode.workspace.fs.delete(dest);
@@ -81,7 +81,7 @@ async function getServer(
             "run `cargo xtask install --server` to build the language server from sources. " +
             "If you feel that your platform should be supported, please create an issue " +
             "about that [here](https://github.com/rust-lang/rust-analyzer/issues) and we " +
-            "will consider it."
+            "will consider it.",
     );
     return undefined;
 }
@@ -131,7 +131,7 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
                             } else {
                                 resolve(stdout);
                             }
-                        }
+                        },
                     );
                     handle.stdin?.write(expression);
                     handle.stdin?.end();
@@ -139,6 +139,6 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
             } finally {
                 await vscode.workspace.fs.delete(origFile);
             }
-        }
+        },
     );
 }
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index f721fcce766..ba8546763ec 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -6,9 +6,10 @@ import * as Is from "vscode-languageclient/lib/common/utils/is";
 import { assert } from "./util";
 import * as diagnostics from "./diagnostics";
 import { WorkspaceEdit } from "vscode";
-import { Config, prepareVSCodeConfig } from "./config";
+import { type Config, prepareVSCodeConfig } from "./config";
 import { randomUUID } from "crypto";
 import { sep as pathSeparator } from "path";
+import { unwrapUndefinable } from "./undefinable";
 
 export interface Env {
     [name: string]: string;
@@ -33,21 +34,24 @@ export const LINKED_COMMANDS = new Map<string, ra.CommandLink>();
 // add code to remove a target command from the map after the link is
 // clicked, but assuming most links in hover sheets won't be clicked anyway
 // this code won't change the overall memory use much.
-setInterval(function cleanupOlderCommandLinks() {
-    // keys are returned in insertion order, we'll keep a few
-    // of recent keys available, and clean the rest
-    const keys = [...LINKED_COMMANDS.keys()];
-    const keysToRemove = keys.slice(0, keys.length - 10);
-    for (const key of keysToRemove) {
-        LINKED_COMMANDS.delete(key);
-    }
-}, 10 * 60 * 1000);
+setInterval(
+    function cleanupOlderCommandLinks() {
+        // keys are returned in insertion order, we'll keep a few
+        // of recent keys available, and clean the rest
+        const keys = [...LINKED_COMMANDS.keys()];
+        const keysToRemove = keys.slice(0, keys.length - 10);
+        for (const key of keysToRemove) {
+            LINKED_COMMANDS.delete(key);
+        }
+    },
+    10 * 60 * 1000,
+);
 
 function renderCommand(cmd: ra.CommandLink): string {
     const commandId = randomUUID();
     LINKED_COMMANDS.set(commandId, cmd);
     return `[${cmd.title}](command:rust-analyzer.linkToCommand?${encodeURIComponent(
-        JSON.stringify([commandId])
+        JSON.stringify([commandId]),
     )} '${cmd.tooltip}')`;
 }
 
@@ -56,7 +60,7 @@ function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownStri
         .map(
             (group) =>
                 (group.title ? group.title + " " : "") +
-                group.commands.map(renderCommand).join(" | ")
+                group.commands.map(renderCommand).join(" | "),
         )
         .join("___");
 
@@ -71,7 +75,7 @@ export async function createClient(
     initializationOptions: vscode.WorkspaceConfiguration,
     serverOptions: lc.ServerOptions,
     config: Config,
-    unlinkedFiles: vscode.Uri[]
+    unlinkedFiles: vscode.Uri[],
 ): Promise<lc.LanguageClient> {
     const clientOptions: lc.LanguageClientOptions = {
         documentSelector: [{ scheme: "file", language: "rust" }],
@@ -92,7 +96,7 @@ export async function createClient(
                 async configuration(
                     params: lc.ConfigurationParams,
                     token: vscode.CancellationToken,
-                    next: lc.ConfigurationRequest.HandlerSignature
+                    next: lc.ConfigurationRequest.HandlerSignature,
                 ) {
                     const resp = await next(params, token);
                     if (resp && Array.isArray(resp)) {
@@ -116,7 +120,7 @@ export async function createClient(
             async handleDiagnostics(
                 uri: vscode.Uri,
                 diagnosticList: vscode.Diagnostic[],
-                next: lc.HandleDiagnosticsSignature
+                next: lc.HandleDiagnosticsSignature,
             ) {
                 const preview = config.previewRustcOutput;
                 const errorCode = config.useRustcErrorCode;
@@ -136,20 +140,20 @@ export async function createClient(
                             const folder = vscode.workspace.getWorkspaceFolder(uri)?.uri.fsPath;
                             if (folder) {
                                 const parentBackslash = uri.fsPath.lastIndexOf(
-                                    pathSeparator + "src"
+                                    pathSeparator + "src",
                                 );
                                 const parent = uri.fsPath.substring(0, parentBackslash);
 
                                 if (parent.startsWith(folder)) {
                                     const path = vscode.Uri.file(
-                                        parent + pathSeparator + "Cargo.toml"
+                                        parent + pathSeparator + "Cargo.toml",
                                     );
                                     void vscode.workspace.fs.stat(path).then(async () => {
                                         const choice = await vscode.window.showInformationMessage(
                                             `This rust file does not belong to a loaded cargo project. It looks like it might belong to the workspace at ${path.path}, do you want to add it to the linked Projects?`,
                                             "Yes",
                                             "No",
-                                            "Don't show this again"
+                                            "Don't show this again",
                                         );
                                         switch (choice) {
                                             case undefined:
@@ -167,14 +171,14 @@ export async function createClient(
                                                     config
                                                         .get<any[]>("linkedProjects")
                                                         ?.concat(pathToInsert),
-                                                    false
+                                                    false,
                                                 );
                                                 break;
                                             case "Don't show this again":
                                                 await config.update(
                                                     "showUnlinkedFileNotification",
                                                     false,
-                                                    false
+                                                    false,
                                                 );
                                                 break;
                                         }
@@ -221,7 +225,7 @@ export async function createClient(
                 document: vscode.TextDocument,
                 position: vscode.Position,
                 token: vscode.CancellationToken,
-                _next: lc.ProvideHoverSignature
+                _next: lc.ProvideHoverSignature,
             ) {
                 const editor = vscode.window.activeTextEditor;
                 const positionOrRange = editor?.selection?.contains(position)
@@ -235,7 +239,7 @@ export async function createClient(
                                 client.code2ProtocolConverter.asTextDocumentIdentifier(document),
                             position: positionOrRange,
                         },
-                        token
+                        token,
                     )
                     .then(
                         (result) => {
@@ -249,7 +253,7 @@ export async function createClient(
                         (error) => {
                             client.handleFailedRequest(lc.HoverRequest.type, token, error, null);
                             return Promise.resolve(null);
-                        }
+                        },
                     );
             },
             // Using custom handling of CodeActions to support action groups and snippet edits.
@@ -259,14 +263,14 @@ export async function createClient(
                 range: vscode.Range,
                 context: vscode.CodeActionContext,
                 token: vscode.CancellationToken,
-                _next: lc.ProvideCodeActionsSignature
+                _next: lc.ProvideCodeActionsSignature,
             ) {
                 const params: lc.CodeActionParams = {
                     textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
                     range: client.code2ProtocolConverter.asRange(range),
                     context: await client.code2ProtocolConverter.asCodeActionContext(
                         context,
-                        token
+                        token,
                     ),
                 };
                 return client.sendRequest(lc.CodeActionRequest.type, params, token).then(
@@ -282,21 +286,21 @@ export async function createClient(
                             if (lc.CodeAction.is(item)) {
                                 assert(
                                     !item.command,
-                                    "We don't expect to receive commands in CodeActions"
+                                    "We don't expect to receive commands in CodeActions",
                                 );
                                 const action = await client.protocol2CodeConverter.asCodeAction(
                                     item,
-                                    token
+                                    token,
                                 );
                                 result.push(action);
                                 continue;
                             }
                             assert(
                                 isCodeActionWithoutEditsAndCommands(item),
-                                "We don't expect edits or commands here"
+                                "We don't expect edits or commands here",
                             );
                             const kind = client.protocol2CodeConverter.asCodeActionKind(
-                                (item as any).kind
+                                (item as any).kind,
                             );
                             const action = new vscode.CodeAction(item.title, kind);
                             const group = (item as any).group;
@@ -323,10 +327,12 @@ export async function createClient(
                         }
                         for (const [group, { index, items }] of groups) {
                             if (items.length === 1) {
-                                result[index] = items[0];
+                                const item = unwrapUndefinable(items[0]);
+                                result[index] = item;
                             } else {
                                 const action = new vscode.CodeAction(group);
-                                action.kind = items[0].kind;
+                                const item = unwrapUndefinable(items[0]);
+                                action.kind = item.kind;
                                 action.command = {
                                     command: "rust-analyzer.applyActionGroup",
                                     title: "",
@@ -348,7 +354,7 @@ export async function createClient(
                         }
                         return result;
                     },
-                    (_error) => undefined
+                    (_error) => undefined,
                 );
             },
         },
@@ -361,7 +367,7 @@ export async function createClient(
         "rust-analyzer",
         "Rust Analyzer Language Server",
         serverOptions,
-        clientOptions
+        clientOptions,
     );
 
     // To turn on all proposed features use: client.registerProposedFeatures();
@@ -397,7 +403,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
     }
     initialize(
         _capabilities: lc.ServerCapabilities,
-        _documentSelector: lc.DocumentSelector | undefined
+        _documentSelector: lc.DocumentSelector | undefined,
     ): void {}
     dispose(): void {}
 }
@@ -416,7 +422,7 @@ class OverrideFeatures implements lc.StaticFeature {
     }
     initialize(
         _capabilities: lc.ServerCapabilities,
-        _documentSelector: lc.DocumentSelector | undefined
+        _documentSelector: lc.DocumentSelector | undefined,
     ): void {}
     dispose(): void {}
 }
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 98ccd50dc04..e21f536f26a 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -3,23 +3,24 @@ import * as lc from "vscode-languageclient";
 import * as ra from "./lsp_ext";
 import * as path from "path";
 
-import { Ctx, Cmd, CtxInit, discoverWorkspace } from "./ctx";
+import { type Ctx, type Cmd, type CtxInit, discoverWorkspace } from "./ctx";
 import { applySnippetWorkspaceEdit, applySnippetTextEdits } from "./snippets";
 import { spawnSync } from "child_process";
-import { RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run";
+import { type RunnableQuickPick, selectRunnable, createTask, createArgs } from "./run";
 import { AstInspector } from "./ast_inspector";
 import {
     isRustDocument,
     isCargoTomlDocument,
     sleep,
     isRustEditor,
-    RustEditor,
-    RustDocument,
+    type RustEditor,
+    type RustDocument,
 } from "./util";
 import { startDebugSession, makeDebugConfig } from "./debug";
-import { LanguageClient } from "vscode-languageclient/node";
+import type { LanguageClient } from "vscode-languageclient/node";
 import { LINKED_COMMANDS } from "./client";
-import { DependencyId } from "./dependencies_provider";
+import type { DependencyId } from "./dependencies_provider";
+import { unwrapUndefinable } from "./undefinable";
 
 export * from "./ast_inspector";
 export * from "./run";
@@ -47,7 +48,7 @@ export function analyzerStatus(ctx: CtxInit): Cmd {
     })();
 
     ctx.pushExtCleanup(
-        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-status", tdcp)
+        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-status", tdcp),
     );
 
     return async () => {
@@ -79,7 +80,7 @@ export function memoryUsage(ctx: CtxInit): Cmd {
     })();
 
     ctx.pushExtCleanup(
-        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-memory", tdcp)
+        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-memory", tdcp),
     );
 
     return async () => {
@@ -125,11 +126,12 @@ export function matchingBrace(ctx: CtxInit): Cmd {
         const response = await client.sendRequest(ra.matchingBrace, {
             textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
             positions: editor.selections.map((s) =>
-                client.code2ProtocolConverter.asPosition(s.active)
+                client.code2ProtocolConverter.asPosition(s.active),
             ),
         });
         editor.selections = editor.selections.map((sel, idx) => {
-            const active = client.protocol2CodeConverter.asPosition(response[idx]);
+            const position = unwrapUndefinable(response[idx]);
+            const active = client.protocol2CodeConverter.asPosition(position);
             const anchor = sel.isEmpty ? active : sel.anchor;
             return new vscode.Selection(anchor, active);
         });
@@ -194,7 +196,7 @@ export function onEnter(ctx: CtxInit): Cmd {
         const lcEdits = await client
             .sendRequest(ra.onEnter, {
                 textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
-                    editor.document
+                    editor.document,
                 ),
                 position: client.code2ProtocolConverter.asPosition(editor.selection.active),
             })
@@ -231,7 +233,7 @@ export function parentModule(ctx: CtxInit): Cmd {
         if (!locations) return;
 
         if (locations.length === 1) {
-            const loc = locations[0];
+            const loc = unwrapUndefinable(locations[0]);
 
             const uri = client.protocol2CodeConverter.asUri(loc.targetUri);
             const range = client.protocol2CodeConverter.asRange(loc.targetRange);
@@ -247,7 +249,7 @@ export function parentModule(ctx: CtxInit): Cmd {
                 client,
                 uri,
                 position,
-                locations.map((loc) => lc.Location.create(loc.targetUri, loc.targetRange))
+                locations.map((loc) => lc.Location.create(loc.targetUri, loc.targetRange)),
             );
         }
     };
@@ -331,7 +333,13 @@ async function revealParentChain(document: RustDocument, ctx: CtxInit) {
     } while (!ctx.dependencies?.contains(documentPath));
     parentChain.reverse();
     for (const idx in parentChain) {
-        await ctx.treeView?.reveal(parentChain[idx], { select: true, expand: true });
+        const treeView = ctx.treeView;
+        if (!treeView) {
+            continue;
+        }
+
+        const dependency = unwrapUndefinable(parentChain[idx]);
+        await treeView.reveal(dependency, { select: true, expand: true });
     }
 }
 
@@ -349,7 +357,7 @@ export function ssr(ctx: CtxInit): Cmd {
         const position = editor.selection.active;
         const selections = editor.selections;
         const textDocument = client.code2ProtocolConverter.asTextDocumentIdentifier(
-            editor.document
+            editor.document,
         );
 
         const options: vscode.InputBoxOptions = {
@@ -365,7 +373,7 @@ export function ssr(ctx: CtxInit): Cmd {
                         selections,
                     });
                 } catch (e) {
-                    return e.toString();
+                    return String(e);
                 }
                 return null;
             },
@@ -389,9 +397,9 @@ export function ssr(ctx: CtxInit): Cmd {
                 });
 
                 await vscode.workspace.applyEdit(
-                    await client.protocol2CodeConverter.asWorkspaceEdit(edit, token)
+                    await client.protocol2CodeConverter.asWorkspaceEdit(edit, token),
                 );
-            }
+            },
         );
     };
 }
@@ -420,12 +428,12 @@ export function syntaxTree(ctx: CtxInit): Cmd {
             vscode.workspace.onDidChangeTextDocument(
                 this.onDidChangeTextDocument,
                 this,
-                ctx.subscriptions
+                ctx.subscriptions,
             );
             vscode.window.onDidChangeActiveTextEditor(
                 this.onDidChangeActiveTextEditor,
                 this,
-                ctx.subscriptions
+                ctx.subscriptions,
             );
         }
 
@@ -444,7 +452,7 @@ export function syntaxTree(ctx: CtxInit): Cmd {
 
         async provideTextDocumentContent(
             uri: vscode.Uri,
-            ct: vscode.CancellationToken
+            ct: vscode.CancellationToken,
         ): Promise<string> {
             const rustEditor = ctx.activeRustEditor;
             if (!rustEditor) return "";
@@ -467,12 +475,12 @@ export function syntaxTree(ctx: CtxInit): Cmd {
 
     ctx.pushExtCleanup(new AstInspector(ctx));
     ctx.pushExtCleanup(
-        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-syntax-tree", tdcp)
+        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-syntax-tree", tdcp),
     );
     ctx.pushExtCleanup(
         vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
             brackets: [["[", ")"]],
-        })
+        }),
     );
 
     return async () => {
@@ -505,7 +513,7 @@ function viewFileUsingTextDocumentContentProvider(
     requestType: lc.RequestType<lc.TextDocumentPositionParams, string, void>,
     uri: string,
     scheme: string,
-    shouldUpdate: boolean
+    shouldUpdate: boolean,
 ): Cmd {
     const tdcp = new (class implements vscode.TextDocumentContentProvider {
         readonly uri = vscode.Uri.parse(uri);
@@ -514,12 +522,12 @@ function viewFileUsingTextDocumentContentProvider(
             vscode.workspace.onDidChangeTextDocument(
                 this.onDidChangeTextDocument,
                 this,
-                ctx.subscriptions
+                ctx.subscriptions,
             );
             vscode.window.onDidChangeActiveTextEditor(
                 this.onDidChangeActiveTextEditor,
                 this,
-                ctx.subscriptions
+                ctx.subscriptions,
             );
         }
 
@@ -538,7 +546,7 @@ function viewFileUsingTextDocumentContentProvider(
 
         async provideTextDocumentContent(
             _uri: vscode.Uri,
-            ct: vscode.CancellationToken
+            ct: vscode.CancellationToken,
         ): Promise<string> {
             const rustEditor = ctx.activeRustEditor;
             if (!rustEditor) return "";
@@ -546,7 +554,7 @@ function viewFileUsingTextDocumentContentProvider(
             const client = ctx.client;
             const params = {
                 textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
-                    rustEditor.document
+                    rustEditor.document,
                 ),
                 position: client.code2ProtocolConverter.asPosition(rustEditor.selection.active),
             };
@@ -594,7 +602,7 @@ export function interpretFunction(ctx: CtxInit): Cmd {
         ra.interpretFunction,
         uri,
         `rust-analyzer-interpret-function`,
-        false
+        false,
     );
 }
 
@@ -606,12 +614,12 @@ export function viewFileText(ctx: CtxInit): Cmd {
             vscode.workspace.onDidChangeTextDocument(
                 this.onDidChangeTextDocument,
                 this,
-                ctx.subscriptions
+                ctx.subscriptions,
             );
             vscode.window.onDidChangeActiveTextEditor(
                 this.onDidChangeActiveTextEditor,
                 this,
-                ctx.subscriptions
+                ctx.subscriptions,
             );
         }
 
@@ -630,14 +638,14 @@ export function viewFileText(ctx: CtxInit): Cmd {
 
         async provideTextDocumentContent(
             _uri: vscode.Uri,
-            ct: vscode.CancellationToken
+            ct: vscode.CancellationToken,
         ): Promise<string> {
             const rustEditor = ctx.activeRustEditor;
             if (!rustEditor) return "";
             const client = ctx.client;
 
             const params = client.code2ProtocolConverter.asTextDocumentIdentifier(
-                rustEditor.document
+                rustEditor.document,
             );
             return client.sendRequest(ra.viewFileText, params, ct);
         }
@@ -648,7 +656,7 @@ export function viewFileText(ctx: CtxInit): Cmd {
     })();
 
     ctx.pushExtCleanup(
-        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-file-text", tdcp)
+        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-file-text", tdcp),
     );
 
     return async () => {
@@ -669,12 +677,12 @@ export function viewItemTree(ctx: CtxInit): Cmd {
             vscode.workspace.onDidChangeTextDocument(
                 this.onDidChangeTextDocument,
                 this,
-                ctx.subscriptions
+                ctx.subscriptions,
             );
             vscode.window.onDidChangeActiveTextEditor(
                 this.onDidChangeActiveTextEditor,
                 this,
-                ctx.subscriptions
+                ctx.subscriptions,
             );
         }
 
@@ -693,7 +701,7 @@ export function viewItemTree(ctx: CtxInit): Cmd {
 
         async provideTextDocumentContent(
             _uri: vscode.Uri,
-            ct: vscode.CancellationToken
+            ct: vscode.CancellationToken,
         ): Promise<string> {
             const rustEditor = ctx.activeRustEditor;
             if (!rustEditor) return "";
@@ -701,7 +709,7 @@ export function viewItemTree(ctx: CtxInit): Cmd {
 
             const params = {
                 textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
-                    rustEditor.document
+                    rustEditor.document,
                 ),
             };
             return client.sendRequest(ra.viewItemTree, params, ct);
@@ -713,7 +721,7 @@ export function viewItemTree(ctx: CtxInit): Cmd {
     })();
 
     ctx.pushExtCleanup(
-        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-item-tree", tdcp)
+        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-item-tree", tdcp),
     );
 
     return async () => {
@@ -738,7 +746,7 @@ function crateGraph(ctx: CtxInit, full: boolean): Cmd {
                 enableScripts: true,
                 retainContextWhenHidden: true,
                 localResourceRoots: [nodeModulesPath],
-            }
+            },
         );
         const params = {
             full: full,
@@ -827,7 +835,7 @@ export function expandMacro(ctx: CtxInit): Cmd {
 
             const expanded = await client.sendRequest(ra.expandMacro, {
                 textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(
-                    editor.document
+                    editor.document,
                 ),
                 position,
             });
@@ -843,7 +851,7 @@ export function expandMacro(ctx: CtxInit): Cmd {
     })();
 
     ctx.pushExtCleanup(
-        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-expand-macro", tdcp)
+        vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-expand-macro", tdcp),
     );
 
     return async () => {
@@ -875,7 +883,7 @@ export function addProject(ctx: CtxInit): Cmd {
                     return discoverWorkspace([file], discoverProjectCommand, {
                         cwd: path.dirname(file.uri.fsPath),
                     });
-                })
+                }),
         );
 
         ctx.addToDiscoveredWorkspaces(workspaces);
@@ -893,14 +901,14 @@ async function showReferencesImpl(
     client: LanguageClient | undefined,
     uri: string,
     position: lc.Position,
-    locations: lc.Location[]
+    locations: lc.Location[],
 ) {
     if (client) {
         await vscode.commands.executeCommand(
             "editor.action.showReferences",
             vscode.Uri.parse(uri),
             client.protocol2CodeConverter.asPosition(position),
-            locations.map(client.protocol2CodeConverter.asLocation)
+            locations.map(client.protocol2CodeConverter.asLocation),
         );
     }
 }
@@ -917,7 +925,7 @@ export function applyActionGroup(_ctx: CtxInit): Cmd {
         if (!selectedAction) return;
         await vscode.commands.executeCommand(
             "rust-analyzer.resolveCodeAction",
-            selectedAction.arguments
+            selectedAction.arguments,
         );
     };
 }
@@ -992,7 +1000,7 @@ export function resolveCodeAction(ctx: CtxInit): Cmd {
             documentChanges: itemEdit.documentChanges?.filter((change) => "kind" in change),
         };
         const fileSystemEdit = await client.protocol2CodeConverter.asWorkspaceEdit(
-            lcFileSystemEdit
+            lcFileSystemEdit,
         );
         await vscode.workspace.applyEdit(fileSystemEdit);
         await applySnippetWorkspaceEdit(edit);
@@ -1045,12 +1053,12 @@ export function peekTests(ctx: CtxInit): Cmd {
                 const locations: lc.Location[] = tests.map((it) =>
                     lc.Location.create(
                         it.runnable.location!.targetUri,
-                        it.runnable.location!.targetSelectionRange
-                    )
+                        it.runnable.location!.targetSelectionRange,
+                    ),
                 );
 
                 await showReferencesImpl(client, uri, position, locations);
-            }
+            },
         );
     };
 }
@@ -1121,3 +1129,281 @@ export function linkToCommand(_: Ctx): Cmd {
         }
     };
 }
+
+export function viewMemoryLayout(ctx: CtxInit): Cmd {
+    return async () => {
+        const editor = vscode.window.activeTextEditor;
+        if (!editor) return;
+        const client = ctx.client;
+
+        const position = editor.selection.active;
+        const expanded = await client.sendRequest(ra.viewRecursiveMemoryLayout, {
+            textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
+            position,
+        });
+
+        const document = vscode.window.createWebviewPanel(
+            "memory_layout",
+            "[Memory Layout]",
+            vscode.ViewColumn.Two,
+            { enableScripts: true },
+        );
+
+        document.webview.html = `<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+    <style>
+        * {
+            box-sizing: border-box;
+        }
+
+        body {
+            margin: 0;
+            overflow: hidden;
+            min-height: 100%;
+            height: 100vh;
+            padding: 32px;
+            position: relative;
+            display: block;
+
+            background-color: var(--vscode-editor-background);
+            font-family: var(--vscode-editor-font-family);
+            font-size: var(--vscode-editor-font-size);
+            color: var(--vscode-editor-foreground);
+        }
+
+        .container {
+            position: relative;
+        }
+
+        .trans {
+            transition: all 0.2s ease-in-out;
+        }
+
+        .grid {
+            height: 100%;
+            position: relative;
+            color: var(--vscode-commandCenter-activeBorder);
+            pointer-events: none;
+        }
+
+        .grid-line {
+            position: absolute;
+            width: 100%;
+            height: 1px;
+            background-color: var(--vscode-commandCenter-activeBorder);
+        }
+
+        #tooltip {
+            position: fixed;
+            display: none;
+            z-index: 1;
+            pointer-events: none;
+            padding: 4px 8px;
+            z-index: 2;
+
+            color: var(--vscode-editorHoverWidget-foreground);
+            background-color: var(--vscode-editorHoverWidget-background);
+            border: 1px solid var(--vscode-editorHoverWidget-border);
+        }
+
+        #tooltip b {
+            color: var(--vscode-editorInlayHint-typeForeground);
+        }
+
+        #tooltip ul {
+            margin-left: 0;
+            padding-left: 20px;
+        }
+
+        table {
+            position: absolute;
+            transform: rotateZ(90deg) rotateX(180deg);
+            transform-origin: top left;
+            border-collapse: collapse;
+            table-layout: fixed;
+            left: 48px;
+            top: 0;
+            max-height: calc(100vw - 64px - 48px);
+            z-index: 1;
+        }
+
+        td {
+            border: 1px solid var(--vscode-focusBorder);
+            writing-mode: vertical-rl;
+            text-orientation: sideways-right;
+
+            height: 80px;
+        }
+
+        td p {
+            height: calc(100% - 16px);
+            width: calc(100% - 8px);
+            margin: 8px 4px;
+            display: inline-block;
+            transform: rotateY(180deg);
+            pointer-events: none;
+            overflow: hidden;
+        }
+
+        td p * {
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+            display: inline-block;
+            height: 100%;
+        }
+
+        td p b {
+            color: var(--vscode-editorInlayHint-typeForeground);
+        }
+
+        td:hover {
+            background-color: var(--vscode-editor-hoverHighlightBackground);
+        }
+
+        td:empty {
+            visibility: hidden;
+            border: 0;
+        }
+    </style>
+</head>
+<body>
+    <div id="tooltip"></div>
+</body>
+<script>(function() {
+
+const data = ${JSON.stringify(expanded)}
+
+if (!(data && data.nodes.length)) {
+    document.body.innerText = "Not Available"
+    return
+}
+
+data.nodes.map(n => {
+    n.typename = n.typename.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', ' & quot; ').replaceAll("'", '&#039;')
+    return n
+})
+
+let height = window.innerHeight - 64
+
+addEventListener("resize", e => {
+    const new_height = window.innerHeight - 64
+    height = new_height
+    container.classList.remove("trans")
+    table.classList.remove("trans")
+    locate()
+    setTimeout(() => { // give delay to redraw, annoying but needed
+        container.classList.add("trans")
+        table.classList.add("trans")
+    }, 0)
+})
+
+const container = document.createElement("div")
+container.classList.add("container")
+container.classList.add("trans")
+document.body.appendChild(container)
+
+const tooltip = document.getElementById("tooltip")
+
+let y = 0
+let zoom = 1.0
+
+const table = document.createElement("table")
+table.classList.add("trans")
+container.appendChild(table)
+const rows = []
+
+function node_t(idx, depth, offset) {
+    if (!rows[depth]) {
+        rows[depth] = { el: document.createElement("tr"), offset: 0 }
+    }
+
+    if (rows[depth].offset < offset) {
+        const pad = document.createElement("td")
+        pad.colSpan = offset - rows[depth].offset
+        rows[depth].el.appendChild(pad)
+        rows[depth].offset += offset - rows[depth].offset
+    }
+
+    const td = document.createElement("td")
+    td.innerHTML = '<p><span>' + data.nodes[idx].itemName + ':</span> <b>' + data.nodes[idx].typename + '</b></p>'
+
+    td.colSpan = data.nodes[idx].size
+
+    td.addEventListener("mouseover", e => {
+        const node = data.nodes[idx]
+        tooltip.innerHTML = node.itemName + ": <b>" + node.typename + "</b><br/>"
+            + "<ul>"
+            + "<li>size = " + node.size + "</li>"
+            + "<li>align = " + node.alignment + "</li>"
+            + "<li>field offset = " + node.offset + "</li>"
+            + "</ul>"
+            + "<i>double click to focus</i>"
+
+        tooltip.style.display = "block"
+    })
+    td.addEventListener("mouseleave", _ => tooltip.style.display = "none")
+    const total_offset = rows[depth].offset
+    td.addEventListener("dblclick", e => {
+        const node = data.nodes[idx]
+        zoom = data.nodes[0].size / node.size
+        y = -(total_offset) / data.nodes[0].size * zoom
+        x = 0
+        locate()
+    })
+
+    rows[depth].el.appendChild(td)
+    rows[depth].offset += data.nodes[idx].size
+
+
+    if (data.nodes[idx].childrenStart != -1) {
+        for (let i = 0; i < data.nodes[idx].childrenLen; i++) {
+            if (data.nodes[data.nodes[idx].childrenStart + i].size) {
+                node_t(data.nodes[idx].childrenStart + i, depth + 1, offset + data.nodes[data.nodes[idx].childrenStart + i].offset)
+            }
+        }
+    }
+}
+
+node_t(0, 0, 0)
+
+for (const row of rows) table.appendChild(row.el)
+
+const grid = document.createElement("div")
+grid.classList.add("grid")
+container.appendChild(grid)
+
+for (let i = 0; i < data.nodes[0].size / 8 + 1; i++) {
+    const el = document.createElement("div")
+    el.classList.add("grid-line")
+    el.style.top = (i / (data.nodes[0].size / 8) * 100) + "%"
+    el.innerText = i * 8
+    grid.appendChild(el)
+}
+
+addEventListener("mousemove", e => {
+    tooltip.style.top = e.clientY + 10 + "px"
+    tooltip.style.left = e.clientX + 10 + "px"
+})
+
+function locate() {
+    container.style.top = height * y + "px"
+    container.style.height = (height * zoom) + "px"
+
+    table.style.width = container.style.height
+}
+
+locate()
+
+})()
+</script>
+</html>`;
+
+        ctx.pushExtCleanup(document);
+    };
+}
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index c6d2bcc2b2a..a047f9659a9 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -2,8 +2,9 @@ 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 { Env } from "./client";
+import type { Env } from "./client";
 import { log } from "./util";
+import { expectNotUndefined, unwrapUndefinable } from "./undefinable";
 
 export type RunnableEnvCfg =
     | undefined
@@ -37,7 +38,7 @@ export class Config {
         vscode.workspace.onDidChangeConfiguration(
             this.onDidChangeConfiguration,
             this,
-            ctx.subscriptions
+            ctx.subscriptions,
         );
         this.refreshLogging();
         this.configureLanguage();
@@ -63,7 +64,7 @@ export class Config {
         this.configureLanguage();
 
         const requiresReloadOpt = this.requiresReloadOpts.find((opt) =>
-            event.affectsConfiguration(opt)
+            event.affectsConfiguration(opt),
         );
 
         if (!requiresReloadOpt) return;
@@ -98,7 +99,8 @@ export class Config {
         let onEnterRules: vscode.OnEnterRule[] = [
             {
                 // Carry indentation from the previous line
-                beforeText: /^\s*$/,
+                // if it's only whitespace
+                beforeText: /^\s+$/,
                 action: { indentAction: vscode.IndentAction.None },
             },
             {
@@ -208,8 +210,8 @@ export class Config {
                 Object.entries(extraEnv).map(([k, v]) => [
                     k,
                     typeof v !== "string" ? v.toString() : v,
-                ])
-            )
+                ]),
+            ),
         );
     }
     get traceExtension() {
@@ -220,12 +222,16 @@ export class Config {
         return this.get<string[] | undefined>("discoverProjectCommand");
     }
 
+    get problemMatcher(): string[] {
+        return this.get<string[]>("runnables.problemMatcher") || [];
+    }
+
     get cargoRunner() {
         return this.get<string | undefined>("cargoRunner");
     }
 
-    get runnableEnv() {
-        const item = this.get<any>("runnableEnv");
+    get runnablesExtraEnv() {
+        const item = this.get<any>("runnables.extraEnv") ?? this.get<any>("runnableEnv");
         if (!item) return item;
         const fixRecord = (r: Record<string, any>) => {
             for (const key in r) {
@@ -300,7 +306,7 @@ export class Config {
 // to interact with.
 export function prepareVSCodeConfig<T>(
     resp: T,
-    cb?: (key: Extract<keyof T, string>, res: { [key: string]: any }) => void
+    cb?: (key: Extract<keyof T, string>, res: { [key: string]: any }) => void,
 ): T {
     if (Is.string(resp)) {
         return substituteVSCodeVariableInString(resp) as T;
@@ -334,7 +340,7 @@ export function substituteVariablesInEnv(env: Env): Env {
             const depRe = new RegExp(/\${(?<depName>.+?)}/g);
             let match = undefined;
             while ((match = depRe.exec(value))) {
-                const depName = match.groups!.depName;
+                const depName = unwrapUndefinable(match.groups?.["depName"]);
                 deps.add(depName);
                 // `depName` at this point can have a form of `expression` or
                 // `prefix:expression`
@@ -343,7 +349,7 @@ export function substituteVariablesInEnv(env: Env): Env {
                 }
             }
             return [`env:${key}`, { deps: [...deps], value }];
-        })
+        }),
     );
 
     const resolved = new Set<string>();
@@ -352,7 +358,7 @@ export function substituteVariablesInEnv(env: Env): Env {
         if (match) {
             const { prefix, body } = match.groups!;
             if (prefix === "env") {
-                const envName = body;
+                const envName = unwrapUndefinable(body);
                 envWithDeps[dep] = {
                     value: process.env[envName] ?? "",
                     deps: [],
@@ -380,13 +386,12 @@ export function substituteVariablesInEnv(env: Env): Env {
     do {
         leftToResolveSize = toResolve.size;
         for (const key of toResolve) {
-            if (envWithDeps[key].deps.every((dep) => resolved.has(dep))) {
-                envWithDeps[key].value = envWithDeps[key].value.replace(
-                    /\${(?<depName>.+?)}/g,
-                    (_wholeMatch, depName) => {
-                        return envWithDeps[depName].value;
-                    }
-                );
+            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;
+                });
                 resolved.add(key);
                 toResolve.delete(key);
             }
@@ -395,7 +400,8 @@ export function substituteVariablesInEnv(env: Env): Env {
 
     const resolvedEnv: Env = {};
     for (const key of Object.keys(env)) {
-        resolvedEnv[key] = envWithDeps[`env:${key}`].value;
+        const item = unwrapUndefinable(envWithDeps[`env:${key}`]);
+        resolvedEnv[key] = item.value;
     }
     return resolvedEnv;
 }
@@ -414,20 +420,19 @@ function substituteVSCodeVariableInString(val: string): string {
 function computeVscodeVar(varName: string): string | null {
     const workspaceFolder = () => {
         const folders = vscode.workspace.workspaceFolders ?? [];
-        if (folders.length === 1) {
-            // TODO: support for remote workspaces?
-            return folders[0].uri.fsPath;
-        } else if (folders.length > 1) {
-            // could use currently opened document to detect the correct
-            // workspace. However, that would be determined by the document
-            // user has opened on Editor startup. Could lead to
-            // unpredictable workspace selection in practice.
-            // It's better to pick the first one
-            return folders[0].uri.fsPath;
-        } else {
-            // no workspace opened
-            return "";
-        }
+        const folder = folders[0];
+        // TODO: support for remote workspaces?
+        const fsPath: string =
+            folder === undefined
+                ? // no workspace opened
+                  ""
+                : // could use currently opened document to detect the correct
+                  // workspace. However, that would be determined by the document
+                  // 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;
+        return fsPath;
     };
     // https://code.visualstudio.com/docs/editor/variables-reference
     const supportedVariables: { [k: string]: () => string } = {
@@ -444,13 +449,17 @@ function computeVscodeVar(varName: string): string | null {
         // https://github.com/microsoft/vscode/blob/08ac1bb67ca2459496b272d8f4a908757f24f56f/src/vs/workbench/api/common/extHostVariableResolverService.ts#L81
         // or
         // https://github.com/microsoft/vscode/blob/29eb316bb9f154b7870eb5204ec7f2e7cf649bec/src/vs/server/node/remoteTerminalChannel.ts#L56
-        execPath: () => process.env.VSCODE_EXEC_PATH ?? process.execPath,
+        execPath: () => process.env["VSCODE_EXEC_PATH"] ?? process.execPath,
 
         pathSeparator: () => path.sep,
     };
 
     if (varName in supportedVariables) {
-        return supportedVariables[varName]();
+        const fn = expectNotUndefined(
+            supportedVariables[varName],
+            `${varName} should not be undefined here`,
+        );
+        return fn();
     } else {
         // return "${" + varName + "}";
         return null;
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index a72b5391ff1..ac144cbebb2 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -1,5 +1,5 @@
 import * as vscode from "vscode";
-import * as lc from "vscode-languageclient/node";
+import type * as lc from "vscode-languageclient/node";
 import * as ra from "./lsp_ext";
 import * as path from "path";
 
@@ -12,19 +12,19 @@ import {
     isRustEditor,
     LazyOutputChannel,
     log,
-    RustEditor,
+    type RustEditor,
 } from "./util";
-import { ServerStatusParams } from "./lsp_ext";
+import type { ServerStatusParams } from "./lsp_ext";
 import {
-    Dependency,
-    DependencyFile,
+    type Dependency,
+    type DependencyFile,
     RustDependenciesProvider,
-    DependencyId,
+    type DependencyId,
 } from "./dependencies_provider";
 import { execRevealDependency } from "./commands";
 import { PersistentState } from "./persistent_state";
 import { bootstrap } from "./bootstrap";
-import { ExecOptions } from "child_process";
+import type { ExecOptions } from "child_process";
 
 // We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if
 // only those are in use. We use "Empty" to represent these scenarios
@@ -42,10 +42,10 @@ export type Workspace =
 
 export function fetchWorkspace(): Workspace {
     const folders = (vscode.workspace.workspaceFolders || []).filter(
-        (folder) => folder.uri.scheme === "file"
+        (folder) => folder.uri.scheme === "file",
     );
     const rustDocuments = vscode.workspace.textDocuments.filter((document) =>
-        isRustDocument(document)
+        isRustDocument(document),
     );
 
     return folders.length === 0
@@ -61,7 +61,7 @@ export function fetchWorkspace(): Workspace {
 export async function discoverWorkspace(
     files: readonly vscode.TextDocument[],
     command: string[],
-    options: ExecOptions
+    options: ExecOptions,
 ): Promise<JsonProject> {
     const paths = files.map((f) => `"${f.uri.fsPath}"`).join(" ");
     const joinedCommand = command.join(" ");
@@ -110,7 +110,7 @@ export class Ctx {
     constructor(
         readonly extCtx: vscode.ExtensionContext,
         commandFactories: Record<string, CommandFactory>,
-        workspace: Workspace
+        workspace: Workspace,
     ) {
         extCtx.subscriptions.push(this);
         this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
@@ -186,7 +186,7 @@ export class Ctx {
 
                     log.error("Bootstrap error", err);
                     throw new Error(message);
-                }
+                },
             );
             const newEnv = Object.assign({}, process.env, this.config.serverExtraEnv);
             const run: lc.Executable = {
@@ -216,7 +216,7 @@ export class Ctx {
                             return discoverWorkspace([file], discoverProjectCommand, {
                                 cwd: path.dirname(file.uri.fsPath),
                             });
-                        })
+                        }),
                 );
 
                 this.addToDiscoveredWorkspaces(workspaces);
@@ -230,7 +230,7 @@ export class Ctx {
                     if (key === "linkedProjects" && this.config.discoveredWorkspaces.length > 0) {
                         obj["linkedProjects"] = this.config.discoveredWorkspaces;
                     }
-                }
+                },
             );
 
             this._client = await createClient(
@@ -239,17 +239,17 @@ export class Ctx {
                 initializationOptions,
                 serverOptions,
                 this.config,
-                this.unlinkedFiles
+                this.unlinkedFiles,
             );
             this.pushClientCleanup(
                 this._client.onNotification(ra.serverStatus, (params) =>
-                    this.setServerStatus(params)
-                )
+                    this.setServerStatus(params),
+                ),
             );
             this.pushClientCleanup(
                 this._client.onNotification(ra.openServerLogs, () => {
                     this.outputChannel!.show();
-                })
+                }),
             );
         }
         return this._client;
@@ -395,7 +395,7 @@ export class Ctx {
             } else {
                 callback = () =>
                     vscode.window.showErrorMessage(
-                        `command ${fullName} failed: rust-analyzer server is not running`
+                        `command ${fullName} failed: rust-analyzer server is not running`,
                     );
             }
 
@@ -423,7 +423,7 @@ export class Ctx {
                 }
                 statusBar.color = new vscode.ThemeColor("statusBarItem.warningForeground");
                 statusBar.backgroundColor = new vscode.ThemeColor(
-                    "statusBarItem.warningBackground"
+                    "statusBarItem.warningBackground",
                 );
                 statusBar.command = "rust-analyzer.openLogs";
                 icon = "$(warning) ";
@@ -440,7 +440,7 @@ export class Ctx {
             case "stopped":
                 statusBar.tooltip.appendText("Server is stopped");
                 statusBar.tooltip.appendMarkdown(
-                    "\n\n[Start server](command:rust-analyzer.startServer)"
+                    "\n\n[Start server](command:rust-analyzer.startServer)",
                 );
                 statusBar.color = undefined;
                 statusBar.backgroundColor = undefined;
@@ -453,13 +453,13 @@ export class Ctx {
         }
         statusBar.tooltip.appendMarkdown("\n\n[Open logs](command:rust-analyzer.openLogs)");
         statusBar.tooltip.appendMarkdown(
-            "\n\n[Reload Workspace](command:rust-analyzer.reloadWorkspace)"
+            "\n\n[Reload Workspace](command:rust-analyzer.reloadWorkspace)",
         );
         statusBar.tooltip.appendMarkdown(
-            "\n\n[Rebuild Proc Macros](command:rust-analyzer.rebuildProcMacros)"
+            "\n\n[Rebuild Proc Macros](command:rust-analyzer.rebuildProcMacros)",
         );
         statusBar.tooltip.appendMarkdown(
-            "\n\n[Restart server](command:rust-analyzer.restartServer)"
+            "\n\n[Restart server](command:rust-analyzer.restartServer)",
         );
         statusBar.tooltip.appendMarkdown("\n\n[Stop server](command:rust-analyzer.stopServer)");
         if (!status.quiescent) icon = "$(sync~spin) ";
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index cffee1de6af..f3d6238d51b 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -1,18 +1,19 @@
 import * as os from "os";
 import * as vscode from "vscode";
 import * as path from "path";
-import * as ra from "./lsp_ext";
+import type * as ra from "./lsp_ext";
 
 import { Cargo, getRustcId, getSysroot } from "./toolchain";
-import { Ctx } from "./ctx";
+import type { Ctx } from "./ctx";
 import { prepareEnv } from "./run";
+import { unwrapUndefinable } from "./undefinable";
 
 const debugOutput = vscode.window.createOutputChannel("Debug");
 type DebugConfigProvider = (
     config: ra.Runnable,
     executable: string,
     env: Record<string, string>,
-    sourceFileMap?: Record<string, string>
+    sourceFileMap?: Record<string, string>,
 ) => vscode.DebugConfiguration;
 
 export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<void> {
@@ -30,7 +31,7 @@ export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise<
         const answer = await vscode.window.showErrorMessage(
             `Launch configuration '${debugConfig.name}' already exists!`,
             "Cancel",
-            "Update"
+            "Update",
         );
         if (answer === "Cancel") return;
 
@@ -67,7 +68,7 @@ export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promis
 
 async function getDebugConfiguration(
     ctx: Ctx,
-    runnable: ra.Runnable
+    runnable: ra.Runnable,
 ): Promise<vscode.DebugConfiguration | undefined> {
     const editor = ctx.activeRustEditor;
     if (!editor) return;
@@ -91,7 +92,7 @@ async function getDebugConfiguration(
     if (!debugEngine) {
         await vscode.window.showErrorMessage(
             `Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` +
-                ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`
+                ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`,
         );
         return;
     }
@@ -105,12 +106,13 @@ async function getDebugConfiguration(
     const workspaceFolders = vscode.workspace.workspaceFolders!;
     const isMultiFolderWorkspace = workspaceFolders.length > 1;
     const firstWorkspace = workspaceFolders[0];
-    const workspace =
+    const maybeWorkspace =
         !isMultiFolderWorkspace || !runnable.args.workspaceRoot
             ? firstWorkspace
             : workspaceFolders.find((w) => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) ||
               firstWorkspace;
 
+    const workspace = unwrapUndefinable(maybeWorkspace);
     const wsFolder = path.normalize(workspace.uri.fsPath);
     const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : "";
     function simplifyPath(p: string): string {
@@ -118,7 +120,7 @@ async function getDebugConfiguration(
         return path.normalize(p).replace(wsFolder, "${workspaceFolder" + workspaceQualifier + "}");
     }
 
-    const env = prepareEnv(runnable, ctx.config.runnableEnv);
+    const env = prepareEnv(runnable, ctx.config.runnablesExtraEnv);
     const executable = await getDebugExecutable(runnable, env);
     let sourceFileMap = debugOptions.sourceFileMap;
     if (sourceFileMap === "auto") {
@@ -130,12 +132,8 @@ async function getDebugConfiguration(
         sourceFileMap[`/rustc/${commitHash}/`] = rustlib;
     }
 
-    const debugConfig = knownEngines[debugEngine.id](
-        runnable,
-        simplifyPath(executable),
-        env,
-        sourceFileMap
-    );
+    const provider = unwrapUndefinable(knownEngines[debugEngine.id]);
+    const debugConfig = provider(runnable, simplifyPath(executable), env, sourceFileMap);
     if (debugConfig.type in debugOptions.engineSettings) {
         const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
         for (var key in settingsMap) {
@@ -149,8 +147,9 @@ async function getDebugConfiguration(
         debugConfig.name = `run ${path.basename(executable)}`;
     }
 
-    if (debugConfig.cwd) {
-        debugConfig.cwd = simplifyPath(debugConfig.cwd);
+    const cwd = debugConfig["cwd"];
+    if (cwd) {
+        debugConfig["cwd"] = simplifyPath(cwd);
     }
 
     return debugConfig;
@@ -158,7 +157,7 @@ async function getDebugConfiguration(
 
 async function getDebugExecutable(
     runnable: ra.Runnable,
-    env: Record<string, string>
+    env: Record<string, string>,
 ): Promise<string> {
     const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput, env);
     const executable = await cargo.executableFromArgs(runnable.args.cargoArgs);
@@ -171,7 +170,7 @@ function getLldbDebugConfig(
     runnable: ra.Runnable,
     executable: string,
     env: Record<string, string>,
-    sourceFileMap?: Record<string, string>
+    sourceFileMap?: Record<string, string>,
 ): vscode.DebugConfiguration {
     return {
         type: "lldb",
@@ -190,7 +189,7 @@ function getCppvsDebugConfig(
     runnable: ra.Runnable,
     executable: string,
     env: Record<string, string>,
-    sourceFileMap?: Record<string, string>
+    sourceFileMap?: Record<string, string>,
 ): vscode.DebugConfiguration {
     return {
         type: os.platform() === "win32" ? "cppvsdbg" : "cppdbg",
diff --git a/editors/code/src/dependencies_provider.ts b/editors/code/src/dependencies_provider.ts
index 8900aa9a5f3..863ace07801 100644
--- a/editors/code/src/dependencies_provider.ts
+++ b/editors/code/src/dependencies_provider.ts
@@ -1,9 +1,10 @@
 import * as vscode from "vscode";
 import * as fspath from "path";
 import * as fs from "fs";
-import { CtxInit } from "./ctx";
+import type { CtxInit } from "./ctx";
 import * as ra from "./lsp_ext";
-import { FetchDependencyListResult } from "./lsp_ext";
+import type { FetchDependencyListResult } from "./lsp_ext";
+import { unwrapUndefinable } from "./undefinable";
 
 export class RustDependenciesProvider
     implements vscode.TreeDataProvider<Dependency | DependencyFile>
@@ -42,19 +43,24 @@ export class RustDependenciesProvider
     }
 
     getParent?(
-        element: Dependency | DependencyFile
+        element: Dependency | DependencyFile,
     ): vscode.ProviderResult<Dependency | DependencyFile> {
         if (element instanceof Dependency) return undefined;
         return element.parent;
     }
 
     getTreeItem(element: Dependency | DependencyFile): vscode.TreeItem | Thenable<vscode.TreeItem> {
-        if (element.id! in this.dependenciesMap) return this.dependenciesMap[element.id!];
+        const dependenciesMap = this.dependenciesMap;
+        const elementId = element.id!;
+        if (elementId in dependenciesMap) {
+            const dependency = unwrapUndefinable(dependenciesMap[elementId]);
+            return dependency;
+        }
         return element;
     }
 
     getChildren(
-        element?: Dependency | DependencyFile
+        element?: Dependency | DependencyFile,
     ): vscode.ProviderResult<Dependency[] | DependencyFile[]> {
         return new Promise((resolve, _reject) => {
             if (!vscode.workspace.workspaceFolders) {
@@ -81,7 +87,7 @@ export class RustDependenciesProvider
     private async getRootDependencies(): Promise<Dependency[]> {
         const dependenciesResult: FetchDependencyListResult = await this.ctx.client.sendRequest(
             ra.fetchDependencyList,
-            {}
+            {},
         );
         const crates = dependenciesResult.crates;
 
@@ -101,17 +107,17 @@ export class RustDependenciesProvider
             moduleName,
             version,
             vscode.Uri.parse(path).fsPath,
-            vscode.TreeItemCollapsibleState.Collapsed
+            vscode.TreeItemCollapsibleState.Collapsed,
         );
     }
 }
 
 export class Dependency extends vscode.TreeItem {
     constructor(
-        public readonly label: string,
+        public override readonly label: string,
         private version: string,
         readonly dependencyPath: string,
-        public readonly collapsibleState: vscode.TreeItemCollapsibleState
+        public override readonly collapsibleState: vscode.TreeItemCollapsibleState,
     ) {
         super(label, collapsibleState);
         this.resourceUri = vscode.Uri.file(dependencyPath);
@@ -127,10 +133,10 @@ export class Dependency extends vscode.TreeItem {
 
 export class DependencyFile extends vscode.TreeItem {
     constructor(
-        readonly label: string,
+        override readonly label: string,
         readonly dependencyPath: string,
         readonly parent: Dependency | DependencyFile,
-        public readonly collapsibleState: vscode.TreeItemCollapsibleState
+        public override readonly collapsibleState: vscode.TreeItemCollapsibleState,
     ) {
         super(vscode.Uri.file(dependencyPath), collapsibleState);
         this.id = this.resourceUri!.fsPath.toLowerCase();
diff --git a/editors/code/src/diagnostics.ts b/editors/code/src/diagnostics.ts
index 9695d8bf26d..e31a1cdcef9 100644
--- a/editors/code/src/diagnostics.ts
+++ b/editors/code/src/diagnostics.ts
@@ -1,7 +1,14 @@
 import * as anser from "anser";
 import * as vscode from "vscode";
-import { ProviderResult, Range, TextEditorDecorationType, ThemeColor, window } from "vscode";
-import { Ctx } from "./ctx";
+import {
+    type ProviderResult,
+    Range,
+    type TextEditorDecorationType,
+    ThemeColor,
+    window,
+} from "vscode";
+import type { Ctx } from "./ctx";
+import { unwrapUndefinable } from "./undefinable";
 
 export const URI_SCHEME = "rust-analyzer-diagnostics-view";
 
@@ -82,7 +89,7 @@ export class AnsiDecorationProvider implements vscode.Disposable {
     }
 
     private _getDecorations(
-        uri: vscode.Uri
+        uri: vscode.Uri,
     ): ProviderResult<[TextEditorDecorationType, Range[]][]> {
         const stringContents = getRenderedDiagnostic(this.ctx, uri);
         const lines = stringContents.split("\n");
@@ -110,7 +117,7 @@ export class AnsiDecorationProvider implements vscode.Disposable {
                     lineNumber,
                     offset - totalEscapeLength,
                     lineNumber,
-                    offset + content.length - totalEscapeLength
+                    offset + content.length - totalEscapeLength,
                 );
 
                 offset += content.length;
@@ -176,7 +183,7 @@ export class AnsiDecorationProvider implements vscode.Disposable {
 
     private static _convertColor(
         color?: string,
-        truecolor?: string
+        truecolor?: string,
     ): ThemeColor | string | undefined {
         if (!color) {
             return undefined;
@@ -195,7 +202,8 @@ export class AnsiDecorationProvider implements vscode.Disposable {
             // anser won't return both the RGB and the color name at the same time,
             // so just fake a single foreground control char with the palette number:
             const spans = anser.ansiToJson(`\x1b[38;5;${paletteColor}m`);
-            const rgb = spans[1].fg;
+            const span = unwrapUndefinable(spans[1]);
+            const rgb = span.fg;
 
             if (rgb) {
                 return `rgb(${rgb})`;
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index b72804e510c..bb7896973f1 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -27,17 +27,17 @@ export type CommandLinkGroup = {
 // rust-analyzer extensions
 
 export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>(
-    "rust-analyzer/analyzerStatus"
+    "rust-analyzer/analyzerStatus",
 );
 export const cancelFlycheck = new lc.NotificationType0("rust-analyzer/cancelFlycheck");
 export const clearFlycheck = new lc.NotificationType0("rust-analyzer/clearFlycheck");
 export const expandMacro = new lc.RequestType<ExpandMacroParams, ExpandedMacro | null, void>(
-    "rust-analyzer/expandMacro"
+    "rust-analyzer/expandMacro",
 );
 export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage");
 export const openServerLogs = new lc.NotificationType0("rust-analyzer/openServerLogs");
 export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, TestInfo[], void>(
-    "rust-analyzer/relatedTests"
+    "rust-analyzer/relatedTests",
 );
 export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");
 export const rebuildProcMacros = new lc.RequestType0<null, void>("rust-analyzer/rebuildProcMacros");
@@ -47,25 +47,25 @@ export const runFlycheck = new lc.NotificationType<{
 }>("rust-analyzer/runFlycheck");
 export const shuffleCrateGraph = new lc.RequestType0<null, void>("rust-analyzer/shuffleCrateGraph");
 export const syntaxTree = new lc.RequestType<SyntaxTreeParams, string, void>(
-    "rust-analyzer/syntaxTree"
+    "rust-analyzer/syntaxTree",
 );
 export const viewCrateGraph = new lc.RequestType<ViewCrateGraphParams, string, void>(
-    "rust-analyzer/viewCrateGraph"
+    "rust-analyzer/viewCrateGraph",
 );
 export const viewFileText = new lc.RequestType<lc.TextDocumentIdentifier, string, void>(
-    "rust-analyzer/viewFileText"
+    "rust-analyzer/viewFileText",
 );
 export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
-    "rust-analyzer/viewHir"
+    "rust-analyzer/viewHir",
 );
 export const viewMir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
-    "rust-analyzer/viewMir"
+    "rust-analyzer/viewMir",
 );
 export const interpretFunction = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
-    "rust-analyzer/interpretFunction"
+    "rust-analyzer/interpretFunction",
 );
 export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>(
-    "rust-analyzer/viewItemTree"
+    "rust-analyzer/viewItemTree",
 );
 
 export type AnalyzerStatusParams = { textDocument?: lc.TextDocumentIdentifier };
@@ -121,22 +121,22 @@ export type ViewItemTreeParams = { textDocument: lc.TextDocumentIdentifier };
 // experimental extensions
 
 export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], void>(
-    "experimental/joinLines"
+    "experimental/joinLines",
 );
 export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position[], void>(
-    "experimental/matchingBrace"
+    "experimental/matchingBrace",
 );
 export const moveItem = new lc.RequestType<MoveItemParams, lc.TextEdit[], void>(
-    "experimental/moveItem"
+    "experimental/moveItem",
 );
 export const onEnter = new lc.RequestType<lc.TextDocumentPositionParams, lc.TextEdit[], void>(
-    "experimental/onEnter"
+    "experimental/onEnter",
 );
 export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location, void>(
-    "experimental/openCargoToml"
+    "experimental/openCargoToml",
 );
 export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>(
-    "experimental/externalDocs"
+    "experimental/externalDocs",
 );
 export const parentModule = new lc.RequestType<
     lc.TextDocumentPositionParams,
@@ -144,12 +144,17 @@ export const parentModule = new lc.RequestType<
     void
 >("experimental/parentModule");
 export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>(
-    "experimental/runnables"
+    "experimental/runnables",
 );
 export const serverStatus = new lc.NotificationType<ServerStatusParams>(
-    "experimental/serverStatus"
+    "experimental/serverStatus",
 );
 export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>("experimental/ssr");
+export const viewRecursiveMemoryLayout = new lc.RequestType<
+    lc.TextDocumentPositionParams,
+    RecursiveMemoryLayout | null,
+    void
+>("rust-analyzer/viewRecursiveMemoryLayout");
 
 export type JoinLinesParams = {
     textDocument: lc.TextDocumentIdentifier;
@@ -197,3 +202,17 @@ export type SsrParams = {
     position: lc.Position;
     selections: readonly lc.Range[];
 };
+
+export type RecursiveMemoryLayoutNode = {
+    item_name: string;
+    typename: string;
+    size: number;
+    alignment: number;
+    offset: number;
+    parent_idx: number;
+    children_start: number;
+    children_len: number;
+};
+export type RecursiveMemoryLayout = {
+    nodes: RecursiveMemoryLayoutNode[];
+};
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index be9bc9d363c..448150bac06 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -2,7 +2,7 @@ import * as vscode from "vscode";
 import * as lc from "vscode-languageclient/node";
 
 import * as commands from "./commands";
-import { CommandFactory, Ctx, fetchWorkspace } from "./ctx";
+import { type CommandFactory, Ctx, fetchWorkspace } from "./ctx";
 import * as diagnostics from "./diagnostics";
 import { activateTaskProvider } from "./tasks";
 import { setContextValue } from "./util";
@@ -18,7 +18,7 @@ export async function deactivate() {
 }
 
 export async function activate(
-    context: vscode.ExtensionContext
+    context: vscode.ExtensionContext,
 ): Promise<RustAnalyzerExtensionApi> {
     if (vscode.extensions.getExtension("rust-lang.rust")) {
         vscode.window
@@ -26,7 +26,7 @@ export async function activate(
                 `You have both the rust-analyzer (rust-lang.rust-analyzer) and Rust (rust-lang.rust) ` +
                     "plugins enabled. These are known to conflict and cause various functions of " +
                     "both plugins to not work correctly. You should disable one of them.",
-                "Got it"
+                "Got it",
             )
             .then(() => {}, console.error);
     }
@@ -36,7 +36,7 @@ export async function activate(
     // so we do it ourselves.
     const api = await activateServer(ctx).catch((err) => {
         void vscode.window.showErrorMessage(
-            `Cannot activate rust-analyzer extension: ${err.message}`
+            `Cannot activate rust-analyzer extension: ${err.message}`,
         );
         throw err;
     });
@@ -53,8 +53,8 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
     ctx.pushExtCleanup(
         vscode.workspace.registerTextDocumentContentProvider(
             diagnostics.URI_SCHEME,
-            diagnosticProvider
-        )
+            diagnosticProvider,
+        ),
     );
 
     const decorationProvider = new diagnostics.AnsiDecorationProvider(ctx);
@@ -71,7 +71,7 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
     vscode.workspace.onDidChangeTextDocument(
         async (event) => await decorateVisibleEditors(event.document),
         null,
-        ctx.subscriptions
+        ctx.subscriptions,
     );
     vscode.workspace.onDidOpenTextDocument(decorateVisibleEditors, null, ctx.subscriptions);
     vscode.window.onDidChangeActiveTextEditor(
@@ -82,7 +82,7 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
             }
         },
         null,
-        ctx.subscriptions
+        ctx.subscriptions,
     );
     vscode.window.onDidChangeVisibleTextEditors(
         async (visibleEditors) => {
@@ -92,13 +92,13 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
             }
         },
         null,
-        ctx.subscriptions
+        ctx.subscriptions,
     );
 
     vscode.workspace.onDidChangeWorkspaceFolders(
         async (_) => ctx.onWorkspaceFolderChanges(),
         null,
-        ctx.subscriptions
+        ctx.subscriptions,
     );
     vscode.workspace.onDidChangeConfiguration(
         async (_) => {
@@ -107,7 +107,7 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
             });
         },
         null,
-        ctx.subscriptions
+        ctx.subscriptions,
     );
 
     await ctx.start();
@@ -179,6 +179,7 @@ function createCommands(): Record<string, CommandFactory> {
         runFlycheck: { enabled: commands.runFlycheck },
         ssr: { enabled: commands.ssr },
         serverVersion: { enabled: commands.serverVersion },
+        viewMemoryLayout: { enabled: commands.viewMemoryLayout },
         // Internal commands which are invoked by the server.
         applyActionGroup: { enabled: commands.applyActionGroup },
         applySnippetWorkspaceEdit: { enabled: commands.applySnippetWorkspaceEditCommand },
diff --git a/editors/code/src/nullable.ts b/editors/code/src/nullable.ts
new file mode 100644
index 00000000000..e973e162907
--- /dev/null
+++ b/editors/code/src/nullable.ts
@@ -0,0 +1,19 @@
+export type NotNull<T> = T extends null ? never : T;
+
+export type Nullable<T> = T | null;
+
+function isNotNull<T>(input: Nullable<T>): input is NotNull<T> {
+    return input !== null;
+}
+
+function expectNotNull<T>(input: Nullable<T>, msg: string): NotNull<T> {
+    if (isNotNull(input)) {
+        return input;
+    }
+
+    throw new TypeError(msg);
+}
+
+export function unwrapNullable<T>(input: Nullable<T>): NotNull<T> {
+    return expectNotNull(input, `unwrapping \`null\``);
+}
diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts
index 8964a78dc32..cebd16a3c90 100644
--- a/editors/code/src/persistent_state.ts
+++ b/editors/code/src/persistent_state.ts
@@ -1,4 +1,4 @@
-import * as vscode from "vscode";
+import type * as vscode from "vscode";
 import { log } from "./util";
 
 export class PersistentState {
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index 623fb102953..c893d390554 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -1,11 +1,12 @@
 import * as vscode from "vscode";
-import * as lc from "vscode-languageclient";
+import type * as lc from "vscode-languageclient";
 import * as ra from "./lsp_ext";
 import * as tasks from "./tasks";
 
-import { CtxInit } from "./ctx";
+import type { CtxInit } from "./ctx";
 import { makeDebugConfig } from "./debug";
-import { Config, RunnableEnvCfg } from "./config";
+import type { Config, RunnableEnvCfg } from "./config";
+import { unwrapUndefinable } from "./undefinable";
 
 const quickPickButtons = [
     { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." },
@@ -15,7 +16,7 @@ export async function selectRunnable(
     ctx: CtxInit,
     prevRunnable?: RunnableQuickPick,
     debuggeeOnly = false,
-    showButtons: boolean = true
+    showButtons: boolean = true,
 ): Promise<RunnableQuickPick | undefined> {
     const editor = ctx.activeRustEditor;
     if (!editor) return;
@@ -68,12 +69,14 @@ export async function selectRunnable(
             quickPick.onDidHide(() => close()),
             quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
             quickPick.onDidTriggerButton(async (_button) => {
-                await makeDebugConfig(ctx, quickPick.activeItems[0].runnable);
+                const runnable = unwrapUndefinable(quickPick.activeItems[0]).runnable;
+                await makeDebugConfig(ctx, runnable);
                 close();
             }),
-            quickPick.onDidChangeActive((active) => {
-                if (showButtons && active.length > 0) {
-                    if (active[0].label.startsWith("cargo")) {
+            quickPick.onDidChangeActive((activeList) => {
+                if (showButtons && activeList.length > 0) {
+                    const active = unwrapUndefinable(activeList[0]);
+                    if (active.label.startsWith("cargo")) {
                         // save button makes no sense for `cargo test` or `cargo check`
                         quickPick.buttons = [];
                     } else if (quickPick.buttons.length === 0) {
@@ -81,7 +84,7 @@ export async function selectRunnable(
                     }
                 }
             }),
-            quickPick
+            quickPick,
         );
         quickPick.show();
     });
@@ -100,7 +103,7 @@ export class RunnableQuickPick implements vscode.QuickPickItem {
 
 export function prepareEnv(
     runnable: ra.Runnable,
-    runnableEnvCfg: RunnableEnvCfg
+    runnableEnvCfg: RunnableEnvCfg,
 ): Record<string, string> {
     const env: Record<string, string> = { RUST_BACKTRACE: "short" };
 
@@ -140,7 +143,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
         command: args[0], // run, test, etc...
         args: args.slice(1),
         cwd: runnable.args.workspaceRoot || ".",
-        env: prepareEnv(runnable, config.runnableEnv),
+        env: prepareEnv(runnable, config.runnablesExtraEnv),
         overrideCargo: runnable.args.overrideCargo,
     };
 
@@ -151,8 +154,9 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
         definition,
         runnable.label,
         args,
+        config.problemMatcher,
         config.cargoRunner,
-        true
+        true,
     );
 
     cargoTask.presentationOptions.clear = true;
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts
index 299d29c27ee..d81765649ff 100644
--- a/editors/code/src/snippets.ts
+++ b/editors/code/src/snippets.ts
@@ -1,10 +1,11 @@
 import * as vscode from "vscode";
 
 import { assert } from "./util";
+import { unwrapUndefinable } from "./undefinable";
 
 export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
     if (edit.entries().length === 1) {
-        const [uri, edits] = edit.entries()[0];
+        const [uri, edits] = unwrapUndefinable(edit.entries()[0]);
         const editor = await editorFromUri(uri);
         if (editor) await applySnippetTextEdits(editor, edits);
         return;
@@ -16,7 +17,9 @@ export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
                 for (const indel of edits) {
                     assert(
                         !parseSnippet(indel.newText),
-                        `bad ws edit: snippet received with multiple edits: ${JSON.stringify(edit)}`
+                        `bad ws edit: snippet received with multiple edits: ${JSON.stringify(
+                            edit,
+                        )}`,
                     );
                     builder.replace(indel.range, indel.newText);
                 }
@@ -31,7 +34,7 @@ async function editorFromUri(uri: vscode.Uri): Promise<vscode.TextEditor | undef
         await vscode.window.showTextDocument(uri, {});
     }
     return vscode.window.visibleTextEditors.find(
-        (it) => it.document.uri.toString() === uri.toString()
+        (it) => it.document.uri.toString() === uri.toString(),
     );
 }
 
@@ -55,8 +58,8 @@ export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vs
                 selections.push(
                     new vscode.Selection(
                         new vscode.Position(startLine, startColumn),
-                        new vscode.Position(startLine, endColumn)
-                    )
+                        new vscode.Position(startLine, endColumn),
+                    ),
                 );
                 builder.replace(indel.range, newText);
             } else {
@@ -68,7 +71,8 @@ export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vs
     });
     if (selections.length > 0) editor.selections = selections;
     if (selections.length === 1) {
-        editor.revealRange(selections[0], vscode.TextEditorRevealType.InCenterIfOutsideViewport);
+        const selection = unwrapUndefinable(selections[0]);
+        editor.revealRange(selection, vscode.TextEditorRevealType.InCenterIfOutsideViewport);
     }
 }
 
diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts
index d6509d9aa6e..1d5ab82aa04 100644
--- a/editors/code/src/tasks.ts
+++ b/editors/code/src/tasks.ts
@@ -1,7 +1,8 @@
 import * as vscode from "vscode";
 import * as toolchain from "./toolchain";
-import { Config } from "./config";
+import type { Config } from "./config";
 import { log } from "./util";
+import { unwrapUndefinable } from "./undefinable";
 
 // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
 // our configuration should be compatible with it so use the same key.
@@ -46,7 +47,8 @@ class CargoTaskProvider implements vscode.TaskProvider {
                     { type: TASK_TYPE, command: def.command },
                     `cargo ${def.command}`,
                     [def.command],
-                    this.config.cargoRunner
+                    this.config.problemMatcher,
+                    this.config.cargoRunner,
                 );
                 vscodeTask.group = def.group;
                 tasks.push(vscodeTask);
@@ -70,7 +72,8 @@ class CargoTaskProvider implements vscode.TaskProvider {
                 definition,
                 task.name,
                 args,
-                this.config.cargoRunner
+                this.config.problemMatcher,
+                this.config.cargoRunner,
             );
         }
 
@@ -83,8 +86,9 @@ export async function buildCargoTask(
     definition: CargoTaskDefinition,
     name: string,
     args: string[],
+    problemMatcher: string[],
     customRunner?: string,
-    throwOnError: boolean = false
+    throwOnError: boolean = false,
 ): Promise<vscode.Task> {
     let exec: vscode.ProcessExecution | vscode.ShellExecution | undefined = undefined;
 
@@ -117,7 +121,8 @@ export async function buildCargoTask(
 
         const fullCommand = [...cargoCommand, ...args];
 
-        exec = new vscode.ProcessExecution(fullCommand[0], fullCommand.slice(1), definition);
+        const processName = unwrapUndefinable(fullCommand[0]);
+        exec = new vscode.ProcessExecution(processName, fullCommand.slice(1), definition);
     }
 
     return new vscode.Task(
@@ -128,7 +133,7 @@ export async function buildCargoTask(
         name,
         TASK_SOURCE,
         exec,
-        ["$rustc", "$rust-panic"]
+        problemMatcher,
     );
 }
 
diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts
index 917a1d6b099..58e5fc747a1 100644
--- a/editors/code/src/toolchain.ts
+++ b/editors/code/src/toolchain.ts
@@ -4,6 +4,8 @@ import * as path from "path";
 import * as readline from "readline";
 import * as vscode from "vscode";
 import { execute, log, memoizeAsync } from "./util";
+import { unwrapNullable } from "./nullable";
+import { unwrapUndefinable } from "./undefinable";
 
 interface CompilationArtifact {
     fileName: string;
@@ -21,7 +23,7 @@ export class Cargo {
     constructor(
         readonly rootFolder: string,
         readonly output: vscode.OutputChannel,
-        readonly env: Record<string, string>
+        readonly env: Record<string, string>,
     ) {}
 
     // Made public for testing purposes
@@ -74,7 +76,7 @@ export class Cargo {
                         this.output.append(message.message.rendered);
                     }
                 },
-                (stderr) => this.output.append(stderr)
+                (stderr) => this.output.append(stderr),
             );
         } catch (err) {
             this.output.show(true);
@@ -93,13 +95,14 @@ export class Cargo {
             throw new Error("Multiple compilation artifacts are not supported.");
         }
 
-        return artifacts[0].fileName;
+        const artifact = unwrapUndefinable(artifacts[0]);
+        return artifact.fileName;
     }
 
     private async runCargo(
         cargoArgs: string[],
         onStdoutJson: (obj: any) => void,
-        onStderrString: (data: string) => void
+        onStderrString: (data: string) => void,
     ): Promise<number> {
         const path = await cargoPath();
         return await new Promise((resolve, reject) => {
@@ -142,7 +145,9 @@ export async function getRustcId(dir: string): Promise<string> {
     const data = await execute(`${rustcPath} -V -v`, { cwd: dir });
     const rx = /commit-hash:\s(.*)$/m;
 
-    return rx.exec(data)![1];
+    const result = unwrapNullable(rx.exec(data));
+    const first = unwrapUndefinable(result[1]);
+    return first;
 }
 
 /** Mirrors `toolchain::cargo()` implementation */
@@ -167,11 +172,11 @@ export const getPathForExecutable = memoizeAsync(
             if (await isFileAtUri(standardPath)) return standardPath.fsPath;
         }
         return executableName;
-    }
+    },
 );
 
 async function lookupInPath(exec: string): Promise<boolean> {
-    const paths = process.env.PATH ?? "";
+    const paths = process.env["PATH"] ?? "";
 
     const candidates = paths.split(path.delimiter).flatMap((dirInPath) => {
         const candidate = path.join(dirInPath, exec);
diff --git a/editors/code/src/undefinable.ts b/editors/code/src/undefinable.ts
new file mode 100644
index 00000000000..813bac5a123
--- /dev/null
+++ b/editors/code/src/undefinable.ts
@@ -0,0 +1,19 @@
+export type NotUndefined<T> = T extends undefined ? never : T;
+
+export type Undefinable<T> = T | undefined;
+
+function isNotUndefined<T>(input: Undefinable<T>): input is NotUndefined<T> {
+    return input !== undefined;
+}
+
+export function expectNotUndefined<T>(input: Undefinable<T>, msg: string): NotUndefined<T> {
+    if (isNotUndefined(input)) {
+        return input;
+    }
+
+    throw new TypeError(msg);
+}
+
+export function unwrapUndefinable<T>(input: Undefinable<T>): NotUndefined<T> {
+    return expectNotUndefined(input, `unwrapping \`undefined\``);
+}
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index b6b779e2660..38ce6761578 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -1,7 +1,6 @@
-import * as lc from "vscode-languageclient/node";
 import * as vscode from "vscode";
 import { strict as nativeAssert } from "assert";
-import { exec, ExecOptions, spawnSync } from "child_process";
+import { exec, type ExecOptions, spawnSync } from "child_process";
 import { inspect } from "util";
 
 export function assert(condition: boolean, explanation: string): asserts condition {
@@ -57,37 +56,6 @@ export const log = new (class {
     }
 })();
 
-export async function sendRequestWithRetry<TParam, TRet>(
-    client: lc.LanguageClient,
-    reqType: lc.RequestType<TParam, TRet, unknown>,
-    param: TParam,
-    token?: vscode.CancellationToken
-): Promise<TRet> {
-    // The sequence is `10 * (2 ** (2 * n))` where n is 1, 2, 3...
-    for (const delay of [40, 160, 640, 2560, 10240, null]) {
-        try {
-            return await (token
-                ? client.sendRequest(reqType, param, token)
-                : client.sendRequest(reqType, param));
-        } catch (error) {
-            if (delay === null) {
-                log.warn("LSP request timed out", { method: reqType.method, param, error });
-                throw error;
-            }
-            if (error.code === lc.LSPErrorCodes.RequestCancelled) {
-                throw error;
-            }
-
-            if (error.code !== lc.LSPErrorCodes.ContentModified) {
-                log.warn("LSP request failed", { method: reqType.method, param, error });
-                throw error;
-            }
-            await sleep(delay);
-        }
-    }
-    throw "unreachable";
-}
-
 export function sleep(ms: number) {
     return new Promise((resolve) => setTimeout(resolve, ms));
 }
@@ -146,7 +114,7 @@ export function setContextValue(key: string, value: any): Thenable<void> {
  * underlying async function.
  */
 export function memoizeAsync<Ret, TThis, Param extends string>(
-    func: (this: TThis, arg: Param) => Promise<Ret>
+    func: (this: TThis, arg: Param) => Promise<Ret>,
 ) {
     const cache = new Map<string, Ret>();