about summary refs log tree commit diff
path: root/editors/code/src
diff options
context:
space:
mode:
authorLaurențiu Nicola <lnicola@dend.ro>2023-02-13 13:55:14 +0200
committerLaurențiu Nicola <lnicola@dend.ro>2023-02-13 13:55:14 +0200
commitbc45c7659a4668206922c290dfc1b78b7e57d375 (patch)
tree327b3415d0ea1328ff60ee7862deca8fc9b72ae3 /editors/code/src
parent3e0e51c108ad38aec4225769711df9c99df6347c (diff)
downloadrust-bc45c7659a4668206922c290dfc1b78b7e57d375.tar.gz
rust-bc45c7659a4668206922c290dfc1b78b7e57d375.zip
:arrow_up: rust-analyzer
Diffstat (limited to 'editors/code/src')
-rw-r--r--editors/code/src/bootstrap.ts96
-rw-r--r--editors/code/src/client.ts60
-rw-r--r--editors/code/src/commands.ts4
-rw-r--r--editors/code/src/config.ts184
-rw-r--r--editors/code/src/ctx.ts17
-rw-r--r--editors/code/src/debug.ts2
-rw-r--r--editors/code/src/lsp_ext.ts240
-rw-r--r--editors/code/src/util.ts48
8 files changed, 341 insertions, 310 deletions
diff --git a/editors/code/src/bootstrap.ts b/editors/code/src/bootstrap.ts
index 374c3b8144c..b38fa06a85c 100644
--- a/editors/code/src/bootstrap.ts
+++ b/editors/code/src/bootstrap.ts
@@ -31,58 +31,12 @@ export async function bootstrap(
 
     return path;
 }
-
-async function patchelf(dest: vscode.Uri): Promise<void> {
-    await vscode.window.withProgress(
-        {
-            location: vscode.ProgressLocation.Notification,
-            title: "Patching rust-analyzer for NixOS",
-        },
-        async (progress, _) => {
-            const expression = `
-            {srcStr, pkgs ? import <nixpkgs> {}}:
-                pkgs.stdenv.mkDerivation {
-                    name = "rust-analyzer";
-                    src = /. + srcStr;
-                    phases = [ "installPhase" "fixupPhase" ];
-                    installPhase = "cp $src $out";
-                    fixupPhase = ''
-                    chmod 755 $out
-                    patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" $out
-                    '';
-                }
-            `;
-            const origFile = vscode.Uri.file(dest.fsPath + "-orig");
-            await vscode.workspace.fs.rename(dest, origFile, { overwrite: true });
-            try {
-                progress.report({ message: "Patching executable", increment: 20 });
-                await new Promise((resolve, reject) => {
-                    const handle = exec(
-                        `nix-build -E - --argstr srcStr '${origFile.fsPath}' -o '${dest.fsPath}'`,
-                        (err, stdout, stderr) => {
-                            if (err != null) {
-                                reject(Error(stderr));
-                            } else {
-                                resolve(stdout);
-                            }
-                        }
-                    );
-                    handle.stdin?.write(expression);
-                    handle.stdin?.end();
-                });
-            } finally {
-                await vscode.workspace.fs.delete(origFile);
-            }
-        }
-    );
-}
-
 async function getServer(
     context: vscode.ExtensionContext,
     config: Config,
     state: PersistentState
 ): Promise<string | undefined> {
-    const explicitPath = serverPath(config);
+    const explicitPath = process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath;
     if (explicitPath) {
         if (explicitPath.startsWith("~/")) {
             return os.homedir() + explicitPath.slice("~".length);
@@ -131,9 +85,6 @@ async function getServer(
     );
     return undefined;
 }
-function serverPath(config: Config): string | null {
-    return process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath;
-}
 
 async function isNixOs(): Promise<boolean> {
     try {
@@ -146,3 +97,48 @@ async function isNixOs(): Promise<boolean> {
         return false;
     }
 }
+
+async function patchelf(dest: vscode.Uri): Promise<void> {
+    await vscode.window.withProgress(
+        {
+            location: vscode.ProgressLocation.Notification,
+            title: "Patching rust-analyzer for NixOS",
+        },
+        async (progress, _) => {
+            const expression = `
+            {srcStr, pkgs ? import <nixpkgs> {}}:
+                pkgs.stdenv.mkDerivation {
+                    name = "rust-analyzer";
+                    src = /. + srcStr;
+                    phases = [ "installPhase" "fixupPhase" ];
+                    installPhase = "cp $src $out";
+                    fixupPhase = ''
+                    chmod 755 $out
+                    patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" $out
+                    '';
+                }
+            `;
+            const origFile = vscode.Uri.file(dest.fsPath + "-orig");
+            await vscode.workspace.fs.rename(dest, origFile, { overwrite: true });
+            try {
+                progress.report({ message: "Patching executable", increment: 20 });
+                await new Promise((resolve, reject) => {
+                    const handle = exec(
+                        `nix-build -E - --argstr srcStr '${origFile.fsPath}' -o '${dest.fsPath}'`,
+                        (err, stdout, stderr) => {
+                            if (err != null) {
+                                reject(Error(stderr));
+                            } else {
+                                resolve(stdout);
+                            }
+                        }
+                    );
+                    handle.stdin?.write(expression);
+                    handle.stdin?.end();
+                });
+            } finally {
+                await vscode.workspace.fs.delete(origFile);
+            }
+        }
+    );
+}
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 82cdf0390ac..62980ca0464 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -102,24 +102,6 @@ export async function createClient(
                     }
                 },
             },
-            async provideInlayHints(document, viewPort, token, next) {
-                const inlays = await next(document, viewPort, token);
-                if (!inlays) {
-                    return inlays;
-                }
-                // U+200C is a zero-width non-joiner to prevent the editor from forming a ligature
-                // between code and hints
-                for (const inlay of inlays) {
-                    if (typeof inlay.label === "string") {
-                        inlay.label = `\u{200c}${inlay.label}\u{200c}`;
-                    } else if (Array.isArray(inlay.label)) {
-                        for (const it of inlay.label) {
-                            it.value = `\u{200c}${it.value}\u{200c}`;
-                        }
-                    }
-                }
-                return inlays;
-            },
             async handleDiagnostics(
                 uri: vscode.Uri,
                 diagnosticList: vscode.Diagnostic[],
@@ -190,12 +172,10 @@ export async function createClient(
                     )
                     .then(
                         (result) => {
+                            if (!result) return null;
                             const hover = client.protocol2CodeConverter.asHover(result);
-                            if (hover) {
-                                const actions = (<any>result).actions;
-                                if (actions) {
-                                    hover.contents.push(renderHoverActions(actions));
-                                }
+                            if (!!result.actions) {
+                                hover.contents.push(renderHoverActions(result.actions));
                             }
                             return hover;
                         },
@@ -328,25 +308,27 @@ class ExperimentalFeatures implements lc.StaticFeature {
         return { kind: "static" };
     }
     fillClientCapabilities(capabilities: lc.ClientCapabilities): void {
-        const caps: any = capabilities.experimental ?? {};
-        caps.snippetTextEdit = true;
-        caps.codeActionGroup = true;
-        caps.hoverActions = true;
-        caps.serverStatusNotification = true;
-        caps.colorDiagnosticOutput = true;
-        caps.commands = {
-            commands: [
-                "rust-analyzer.runSingle",
-                "rust-analyzer.debugSingle",
-                "rust-analyzer.showReferences",
-                "rust-analyzer.gotoLocation",
-                "editor.action.triggerParameterHints",
-            ],
+        capabilities.experimental = {
+            snippetTextEdit: true,
+            codeActionGroup: true,
+            hoverActions: true,
+            serverStatusNotification: true,
+            colorDiagnosticOutput: true,
+            openServerLogs: true,
+            commands: {
+                commands: [
+                    "rust-analyzer.runSingle",
+                    "rust-analyzer.debugSingle",
+                    "rust-analyzer.showReferences",
+                    "rust-analyzer.gotoLocation",
+                    "editor.action.triggerParameterHints",
+                ],
+            },
+            ...capabilities.experimental,
         };
-        capabilities.experimental = caps;
     }
     initialize(
-        _capabilities: lc.ServerCapabilities<any>,
+        _capabilities: lc.ServerCapabilities,
         _documentSelector: lc.DocumentSelector | undefined
     ): void {}
     dispose(): void {}
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index cb4e13e2c60..b5b64e33e07 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -130,11 +130,11 @@ export function joinLines(ctx: CtxInit): Cmd {
 }
 
 export function moveItemUp(ctx: CtxInit): Cmd {
-    return moveItem(ctx, ra.Direction.Up);
+    return moveItem(ctx, "Up");
 }
 
 export function moveItemDown(ctx: CtxInit): Cmd {
-    return moveItem(ctx, ra.Direction.Down);
+    return moveItem(ctx, "Down");
 }
 
 export function moveItem(ctx: CtxInit, direction: ra.Direction): Cmd {
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index eb4f965291f..1faa0ad9106 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -1,5 +1,6 @@
-import * as path from "path";
+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 { log } from "./util";
@@ -47,7 +48,7 @@ export class Config {
     }
 
     private refreshLogging() {
-        log.setEnabled(this.traceExtension);
+        log.setEnabled(this.traceExtension ?? false);
         log.info("Extension version:", this.package.version);
 
         const cfg = Object.entries(this.cfg).filter(([_, val]) => !(val instanceof Function));
@@ -86,58 +87,84 @@ export class Config {
      * [1]: https://github.com/Microsoft/vscode/issues/11514#issuecomment-244707076
      */
     private configureLanguage() {
-        if (this.typingContinueCommentsOnNewline && !this.configureLang) {
+        // Only need to dispose of the config if there's a change
+        if (this.configureLang) {
+            this.configureLang.dispose();
+            this.configureLang = undefined;
+        }
+
+        let onEnterRules: vscode.OnEnterRule[] = [
+            {
+                // Carry indentation from the previous line
+                beforeText: /^\s*$/,
+                action: { indentAction: vscode.IndentAction.None },
+            },
+            {
+                // After the end of a function/field chain,
+                // with the semicolon on the same line
+                beforeText: /^\s+\..*;/,
+                action: { indentAction: vscode.IndentAction.Outdent },
+            },
+            {
+                // After the end of a function/field chain,
+                // with semicolon detached from the rest
+                beforeText: /^\s+;/,
+                previousLineText: /^\s+\..*/,
+                action: { indentAction: vscode.IndentAction.Outdent },
+            },
+        ];
+
+        if (this.typingContinueCommentsOnNewline) {
             const indentAction = vscode.IndentAction.None;
 
-            this.configureLang = vscode.languages.setLanguageConfiguration("rust", {
-                onEnterRules: [
-                    {
-                        // Doc single-line comment
-                        // e.g. ///|
-                        beforeText: /^\s*\/{3}.*$/,
-                        action: { indentAction, appendText: "/// " },
-                    },
-                    {
-                        // Parent doc single-line comment
-                        // e.g. //!|
-                        beforeText: /^\s*\/{2}\!.*$/,
-                        action: { indentAction, appendText: "//! " },
-                    },
-                    {
-                        // Begins an auto-closed multi-line comment (standard or parent doc)
-                        // e.g. /** | */ or /*! | */
-                        beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/,
-                        afterText: /^\s*\*\/$/,
-                        action: {
-                            indentAction: vscode.IndentAction.IndentOutdent,
-                            appendText: " * ",
-                        },
-                    },
-                    {
-                        // Begins a multi-line comment (standard or parent doc)
-                        // e.g. /** ...| or /*! ...|
-                        beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/,
-                        action: { indentAction, appendText: " * " },
-                    },
-                    {
-                        // Continues a multi-line comment
-                        // e.g.  * ...|
-                        beforeText: /^(\ \ )*\ \*(\ ([^\*]|\*(?!\/))*)?$/,
-                        action: { indentAction, appendText: "* " },
+            onEnterRules = [
+                ...onEnterRules,
+                {
+                    // Doc single-line comment
+                    // e.g. ///|
+                    beforeText: /^\s*\/{3}.*$/,
+                    action: { indentAction, appendText: "/// " },
+                },
+                {
+                    // Parent doc single-line comment
+                    // e.g. //!|
+                    beforeText: /^\s*\/{2}\!.*$/,
+                    action: { indentAction, appendText: "//! " },
+                },
+                {
+                    // Begins an auto-closed multi-line comment (standard or parent doc)
+                    // e.g. /** | */ or /*! | */
+                    beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/,
+                    afterText: /^\s*\*\/$/,
+                    action: {
+                        indentAction: vscode.IndentAction.IndentOutdent,
+                        appendText: " * ",
                     },
-                    {
-                        // Dedents after closing a multi-line comment
-                        // e.g.  */|
-                        beforeText: /^(\ \ )*\ \*\/\s*$/,
-                        action: { indentAction, removeText: 1 },
-                    },
-                ],
-            });
-        }
-        if (!this.typingContinueCommentsOnNewline && this.configureLang) {
-            this.configureLang.dispose();
-            this.configureLang = undefined;
+                },
+                {
+                    // Begins a multi-line comment (standard or parent doc)
+                    // e.g. /** ...| or /*! ...|
+                    beforeText: /^\s*\/\*(\*|\!)(?!\/)([^\*]|\*(?!\/))*$/,
+                    action: { indentAction, appendText: " * " },
+                },
+                {
+                    // Continues a multi-line comment
+                    // e.g.  * ...|
+                    beforeText: /^(\ \ )*\ \*(\ ([^\*]|\*(?!\/))*)?$/,
+                    action: { indentAction, appendText: "* " },
+                },
+                {
+                    // Dedents after closing a multi-line comment
+                    // e.g.  */|
+                    beforeText: /^(\ \ )*\ \*\/\s*$/,
+                    action: { indentAction, removeText: 1 },
+                },
+            ];
         }
+
+        this.configureLang = vscode.languages.setLanguageConfiguration("rust", {
+            onEnterRules,
+        });
     }
 
     // We don't do runtime config validation here for simplicity. More on stackoverflow:
@@ -163,18 +190,24 @@ export class Config {
      * ```
      * So this getter handles this quirk by not requiring the caller to use postfix `!`
      */
-    private get<T>(path: string): T {
-        return this.cfg.get<T>(path)!;
+    private get<T>(path: string): T | undefined {
+        return substituteVSCodeVariables(this.cfg.get<T>(path));
     }
 
     get serverPath() {
         return this.get<null | string>("server.path") ?? this.get<null | string>("serverPath");
     }
+
     get serverExtraEnv(): Env {
         const extraEnv =
             this.get<{ [key: string]: string | number } | null>("server.extraEnv") ?? {};
-        return Object.fromEntries(
-            Object.entries(extraEnv).map(([k, v]) => [k, typeof v !== "string" ? v.toString() : v])
+        return substituteVariablesInEnv(
+            Object.fromEntries(
+                Object.entries(extraEnv).map(([k, v]) => [
+                    k,
+                    typeof v !== "string" ? v.toString() : v,
+                ])
+            )
         );
     }
     get traceExtension() {
@@ -216,13 +249,13 @@ export class Config {
         if (sourceFileMap !== "auto") {
             // "/rustc/<id>" used by suggestions only.
             const { ["/rustc/<id>"]: _, ...trimmed } =
-                this.get<Record<string, string>>("debug.sourceFileMap");
+                this.get<Record<string, string>>("debug.sourceFileMap") ?? {};
             sourceFileMap = trimmed;
         }
 
         return {
             engine: this.get<string>("debug.engine"),
-            engineSettings: this.get<object>("debug.engineSettings"),
+            engineSettings: this.get<object>("debug.engineSettings") ?? {},
             openDebugPane: this.get<boolean>("debug.openDebugPane"),
             sourceFileMap: sourceFileMap,
         };
@@ -247,37 +280,25 @@ export class Config {
     }
 }
 
-const VarRegex = new RegExp(/\$\{(.+?)\}/g);
-
-export function substituteVSCodeVariableInString(val: string): string {
-    return val.replace(VarRegex, (substring: string, varName) => {
-        if (typeof varName === "string") {
-            return computeVscodeVar(varName) || substring;
-        } else {
-            return substring;
-        }
-    });
-}
-
-export function substituteVSCodeVariables(resp: any): any {
-    if (typeof resp === "string") {
-        return substituteVSCodeVariableInString(resp);
-    } else if (resp && Array.isArray(resp)) {
+export function substituteVSCodeVariables<T>(resp: T): T {
+    if (Is.string(resp)) {
+        return substituteVSCodeVariableInString(resp) as T;
+    } else if (resp && Is.array<any>(resp)) {
         return resp.map((val) => {
             return substituteVSCodeVariables(val);
-        });
+        }) as T;
     } else if (resp && typeof resp === "object") {
         const res: { [key: string]: any } = {};
         for (const key in resp) {
             const val = resp[key];
             res[key] = substituteVSCodeVariables(val);
         }
-        return res;
-    } else if (typeof resp === "function") {
-        return null;
+        return res as T;
     }
     return resp;
 }
+
+// FIXME: Merge this with `substituteVSCodeVariables` above
 export function substituteVariablesInEnv(env: Env): Env {
     const missingDeps = new Set<string>();
     // vscode uses `env:ENV_NAME` for env vars resolution, and it's easier
@@ -355,6 +376,17 @@ export function substituteVariablesInEnv(env: Env): Env {
     return resolvedEnv;
 }
 
+const VarRegex = new RegExp(/\$\{(.+?)\}/g);
+function substituteVSCodeVariableInString(val: string): string {
+    return val.replace(VarRegex, (substring: string, varName) => {
+        if (Is.string(varName)) {
+            return computeVscodeVar(varName) || substring;
+        } else {
+            return substring;
+        }
+    });
+}
+
 function computeVscodeVar(varName: string): string | null {
     const workspaceFolder = () => {
         const folders = vscode.workspace.workspaceFolders ?? [];
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index d6cee5c8fc6..e2a30e0cc45 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -2,9 +2,9 @@ import * as vscode from "vscode";
 import * as lc from "vscode-languageclient/node";
 import * as ra from "./lsp_ext";
 
-import { Config, substituteVariablesInEnv, substituteVSCodeVariables } from "./config";
+import { Config, substituteVSCodeVariables } from "./config";
 import { createClient } from "./client";
-import { isRustDocument, isRustEditor, log, RustEditor } from "./util";
+import { isRustDocument, isRustEditor, LazyOutputChannel, log, RustEditor } from "./util";
 import { ServerStatusParams } from "./lsp_ext";
 import { PersistentState } from "./persistent_state";
 import { bootstrap } from "./bootstrap";
@@ -128,9 +128,7 @@ export class Ctx {
         }
 
         if (!this.traceOutputChannel) {
-            this.traceOutputChannel = vscode.window.createOutputChannel(
-                "Rust Analyzer Language Server Trace"
-            );
+            this.traceOutputChannel = new LazyOutputChannel("Rust Analyzer Language Server Trace");
             this.pushExtCleanup(this.traceOutputChannel);
         }
         if (!this.outputChannel) {
@@ -152,9 +150,7 @@ export class Ctx {
                     throw new Error(message);
                 }
             );
-            const newEnv = substituteVariablesInEnv(
-                Object.assign({}, process.env, this.config.serverExtraEnv)
-            );
+            const newEnv = Object.assign({}, process.env, this.config.serverExtraEnv);
             const run: lc.Executable = {
                 command: this._serverPath,
                 options: { env: newEnv },
@@ -187,6 +183,11 @@ export class Ctx {
                     this.setServerStatus(params)
                 )
             );
+            this.pushClientCleanup(
+                this._client.onNotification(ra.openServerLogs, () => {
+                    this.outputChannel!.show();
+                })
+            );
         }
         return this._client;
     }
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index bd45599227e..268b70b4fbb 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -84,7 +84,7 @@ async function getDebugConfiguration(
             debugEngine = vscode.extensions.getExtension(engineId);
             if (debugEngine) break;
         }
-    } else {
+    } else if (debugOptions.engine) {
         debugEngine = vscode.extensions.getExtension(debugOptions.engine);
     }
 
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 29349cc20f5..f6f5124dc41 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -4,130 +4,134 @@
 
 import * as lc from "vscode-languageclient";
 
-export interface AnalyzerStatusParams {
-    textDocument?: lc.TextDocumentIdentifier;
-}
+// rust-analyzer overrides
+
+export const hover = new lc.RequestType<
+    HoverParams,
+    (lc.Hover & { actions: CommandLinkGroup[] }) | null,
+    void
+>("textDocument/hover");
+export type HoverParams = { position: lc.Position | lc.Range } & Omit<
+    lc.TextDocumentPositionParams,
+    "position"
+> &
+    lc.WorkDoneProgressParams;
+export type CommandLink = {
+    /**
+     * A tooltip for the command, when represented in the UI.
+     */
+    tooltip?: string;
+} & lc.Command;
+export type CommandLinkGroup = {
+    title?: string;
+    commands: CommandLink[];
+};
+
+// rust-analyzer extensions
+
 export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>(
     "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"
+);
 export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage");
-export const shuffleCrateGraph = new lc.RequestType0<null, void>("rust-analyzer/shuffleCrateGraph");
-
-export interface ServerStatusParams {
-    health: "ok" | "warning" | "error";
-    quiescent: boolean;
-    message?: string;
-}
-export const serverStatus = new lc.NotificationType<ServerStatusParams>(
-    "experimental/serverStatus"
+export const openServerLogs = new lc.NotificationType0("rust-analyzer/openServerLogs");
+export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, TestInfo[], void>(
+    "rust-analyzer/relatedTests"
 );
-
 export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");
-
-export const hover = new lc.RequestType<HoverParams, lc.Hover | null, void>("textDocument/hover");
-
-export interface HoverParams extends lc.WorkDoneProgressParams {
-    textDocument: lc.TextDocumentIdentifier;
-    position: lc.Range | lc.Position;
-}
-
-export interface SyntaxTreeParams {
-    textDocument: lc.TextDocumentIdentifier;
-    range: lc.Range | null;
-}
+export const runFlycheck = new lc.NotificationType<{
+    textDocument: lc.TextDocumentIdentifier | null;
+}>("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"
 );
-
-export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
-    "rust-analyzer/viewHir"
+export const viewCrateGraph = new lc.RequestType<ViewCrateGraphParams, string, void>(
+    "rust-analyzer/viewCrateGraph"
 );
-
 export const viewFileText = new lc.RequestType<lc.TextDocumentIdentifier, string, void>(
     "rust-analyzer/viewFileText"
 );
-
-export interface ViewItemTreeParams {
-    textDocument: lc.TextDocumentIdentifier;
-}
-
+export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
+    "rust-analyzer/viewHir"
+);
 export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>(
     "rust-analyzer/viewItemTree"
 );
 
-export interface ViewCrateGraphParams {
-    full: boolean;
-}
+export type AnalyzerStatusParams = { textDocument?: lc.TextDocumentIdentifier };
 
-export const viewCrateGraph = new lc.RequestType<ViewCrateGraphParams, string, void>(
-    "rust-analyzer/viewCrateGraph"
-);
-
-export interface ExpandMacroParams {
+export type ExpandMacroParams = {
     textDocument: lc.TextDocumentIdentifier;
     position: lc.Position;
-}
-export interface ExpandedMacro {
+};
+export type ExpandedMacro = {
     name: string;
     expansion: string;
-}
-export const expandMacro = new lc.RequestType<ExpandMacroParams, ExpandedMacro | null, void>(
-    "rust-analyzer/expandMacro"
-);
-
-export const relatedTests = new lc.RequestType<lc.TextDocumentPositionParams, TestInfo[], void>(
-    "rust-analyzer/relatedTests"
-);
-
-export const cancelFlycheck = new lc.NotificationType0("rust-analyzer/cancelFlycheck");
-export const clearFlycheck = new lc.NotificationType0("rust-analyzer/clearFlycheck");
-export const runFlycheck = new lc.NotificationType<{
-    textDocument: lc.TextDocumentIdentifier | null;
-}>("rust-analyzer/runFlycheck");
-
-// Experimental extensions
-
-export interface SsrParams {
-    query: string;
-    parseOnly: boolean;
+};
+export type TestInfo = { runnable: Runnable };
+export type SyntaxTreeParams = {
     textDocument: lc.TextDocumentIdentifier;
-    position: lc.Position;
-    selections: readonly lc.Range[];
-}
-export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>("experimental/ssr");
+    range: lc.Range | null;
+};
+export type ViewCrateGraphParams = { full: boolean };
+export type ViewItemTreeParams = { textDocument: lc.TextDocumentIdentifier };
 
-export interface MatchingBraceParams {
-    textDocument: lc.TextDocumentIdentifier;
-    positions: lc.Position[];
-}
+// experimental extensions
+
+export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], void>(
+    "experimental/joinLines"
+);
 export const matchingBrace = new lc.RequestType<MatchingBraceParams, lc.Position[], void>(
     "experimental/matchingBrace"
 );
-
+export const moveItem = new lc.RequestType<MoveItemParams, lc.TextEdit[], void>(
+    "experimental/moveItem"
+);
+export const onEnter = new lc.RequestType<lc.TextDocumentPositionParams, lc.TextEdit[], void>(
+    "experimental/onEnter"
+);
+export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location, void>(
+    "experimental/openCargoToml"
+);
+export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>(
+    "experimental/externalDocs"
+);
 export const parentModule = new lc.RequestType<
     lc.TextDocumentPositionParams,
     lc.LocationLink[] | null,
     void
 >("experimental/parentModule");
-
-export interface JoinLinesParams {
-    textDocument: lc.TextDocumentIdentifier;
-    ranges: lc.Range[];
-}
-export const joinLines = new lc.RequestType<JoinLinesParams, lc.TextEdit[], void>(
-    "experimental/joinLines"
+export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>(
+    "experimental/runnables"
 );
-
-export const onEnter = new lc.RequestType<lc.TextDocumentPositionParams, lc.TextEdit[], void>(
-    "experimental/onEnter"
+export const serverStatus = new lc.NotificationType<ServerStatusParams>(
+    "experimental/serverStatus"
 );
+export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>("experimental/ssr");
 
-export interface RunnablesParams {
+export type JoinLinesParams = {
     textDocument: lc.TextDocumentIdentifier;
-    position: lc.Position | null;
-}
-
-export interface Runnable {
+    ranges: lc.Range[];
+};
+export type MatchingBraceParams = {
+    textDocument: lc.TextDocumentIdentifier;
+    positions: lc.Position[];
+};
+export type MoveItemParams = {
+    textDocument: lc.TextDocumentIdentifier;
+    range: lc.Range;
+    direction: Direction;
+};
+export type Direction = "Up" | "Down";
+export type OpenCargoTomlParams = {
+    textDocument: lc.TextDocumentIdentifier;
+};
+export type Runnable = {
     label: string;
     location?: lc.LocationLink;
     kind: "cargo";
@@ -139,50 +143,20 @@ export interface Runnable {
         expectTest?: boolean;
         overrideCargo?: string;
     };
-}
-export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>(
-    "experimental/runnables"
-);
-
-export interface TestInfo {
-    runnable: Runnable;
-}
-
-export interface CommandLink extends lc.Command {
-    /**
-     * A tooltip for the command, when represented in the UI.
-     */
-    tooltip?: string;
-}
-
-export interface CommandLinkGroup {
-    title?: string;
-    commands: CommandLink[];
-}
-
-export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>(
-    "experimental/externalDocs"
-);
-
-export const openCargoToml = new lc.RequestType<OpenCargoTomlParams, lc.Location, void>(
-    "experimental/openCargoToml"
-);
-
-export interface OpenCargoTomlParams {
+};
+export type RunnablesParams = {
     textDocument: lc.TextDocumentIdentifier;
-}
-
-export const moveItem = new lc.RequestType<MoveItemParams, lc.TextEdit[], void>(
-    "experimental/moveItem"
-);
-
-export interface MoveItemParams {
+    position: lc.Position | null;
+};
+export type ServerStatusParams = {
+    health: "ok" | "warning" | "error";
+    quiescent: boolean;
+    message?: string;
+};
+export type SsrParams = {
+    query: string;
+    parseOnly: boolean;
     textDocument: lc.TextDocumentIdentifier;
-    range: lc.Range;
-    direction: Direction;
-}
-
-export const enum Direction {
-    Up = "Up",
-    Down = "Down",
-}
+    position: lc.Position;
+    selections: readonly lc.Range[];
+};
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index cd91932bb60..d93b9caeb16 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -117,7 +117,7 @@ export function isValidExecutable(path: string): boolean {
 
     const res = spawnSync(path, ["--version"], { encoding: "utf8" });
 
-    const printOutput = res.error && (res.error as any).code !== "ENOENT" ? log.warn : log.debug;
+    const printOutput = res.error ? log.warn : log.info;
     printOutput(path, "--version:", res);
 
     return res.status === 0;
@@ -166,3 +166,49 @@ export function execute(command: string, options: ExecOptions): Promise<string>
         });
     });
 }
+
+export class LazyOutputChannel implements vscode.OutputChannel {
+    constructor(name: string) {
+        this.name = name;
+    }
+
+    name: string;
+    _channel: vscode.OutputChannel | undefined;
+
+    get channel(): vscode.OutputChannel {
+        if (!this._channel) {
+            this._channel = vscode.window.createOutputChannel(this.name);
+        }
+        return this._channel;
+    }
+
+    append(value: string): void {
+        this.channel.append(value);
+    }
+    appendLine(value: string): void {
+        this.channel.appendLine(value);
+    }
+    replace(value: string): void {
+        this.channel.replace(value);
+    }
+    clear(): void {
+        if (this._channel) {
+            this._channel.clear();
+        }
+    }
+    show(preserveFocus?: boolean): void;
+    show(column?: vscode.ViewColumn, preserveFocus?: boolean): void;
+    show(column?: any, preserveFocus?: any): void {
+        this.channel.show(column, preserveFocus);
+    }
+    hide(): void {
+        if (this._channel) {
+            this._channel.hide();
+        }
+    }
+    dispose(): void {
+        if (this._channel) {
+            this._channel.dispose();
+        }
+    }
+}