diff --git a/package.json b/package.json index 39bdab87..a10b9fdd 100644 --- a/package.json +++ b/package.json @@ -165,8 +165,11 @@ "description": "Update the RLS whenever the extension starts up." }, "rust-client.channel": { - "type": "string", - "default": "nightly", + "type": [ + "string", + "null" + ], + "default": null, "description": "Rust channel to install RLS from." }, "rust-client.rls-name": { diff --git a/src/configuration.ts b/src/configuration.ts index 813d4d1f..de9c8db7 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -13,6 +13,8 @@ import { workspace, WorkspaceConfiguration } from 'vscode'; import { RevealOutputChannelOn } from 'vscode-languageclient'; +import { getActiveChannel } from './rustup'; + function fromStringToRevealOutputChannelOn(value: string): RevealOutputChannelOn { switch (value && value.toLowerCase()) { case 'info': @@ -58,15 +60,38 @@ export class RLSConfiguration { this.revealOutputChannelOn = RLSConfiguration.readRevealOutputChannelOn(configuration); this.updateOnStartup = configuration.get('rust-client.updateOnStartup', true); - this.channel = configuration.get('rust-client.channel', 'nightly'); + this.channel = RLSConfiguration.readChannel(this.rustupPath, configuration); this.componentName = configuration.get('rust-client.rls-name', 'rls'); // Hidden options that are not exposed to the user this.rlsPath = configuration.get('rls.path', null); this.rlsRoot = configuration.get('rls.root', null); } + private static readRevealOutputChannelOn(configuration: WorkspaceConfiguration) { const setting = configuration.get('rust-client.revealOutputChannelOn', 'never'); return fromStringToRevealOutputChannelOn(setting); } + + /** + * Tries to fetch the `rust-client.channel` configuration value. If missing, + * falls back on active toolchain specified by rustup (at `rustupPath`), + * finally defaulting to `nightly` if all fails. + */ + private static readChannel(rustupPath: string, configuration: WorkspaceConfiguration): string { + const channel = configuration.get('rust-client.channel', null); + if (channel !== null) { + return channel; + } else { + try { + return getActiveChannel(rustupPath); + } + // rustup might not be installed at the time the configuration is + // initially loaded, so silently ignore the error and return a default value + catch (e) { + return 'nightly'; + } + + } + } } diff --git a/src/extension.ts b/src/extension.ts index 9676f4ce..52dccda3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,6 +23,8 @@ import { commands, ExtensionContext, IndentAction, languages, TextEditor, import { LanguageClient, LanguageClientOptions, Location, NotificationType, ServerOptions } from 'vscode-languageclient'; +// FIXME(#233): Don't only rely on lazily initializing it once on startup, +// handle possible `rust-client.*` value changes while extension is running export const CONFIGURATION = RLSConfiguration.loadFromWorkspace(); function getSysroot(env: Object): string | Error { diff --git a/src/rustup.ts b/src/rustup.ts index 6252c2b3..a1d68e7a 100644 --- a/src/rustup.ts +++ b/src/rustup.ts @@ -11,7 +11,7 @@ 'use strict'; import * as child_process from 'child_process'; -import { window } from 'vscode'; +import { window, workspace } from 'vscode'; import { execChildProcess } from './utils/child_process'; import { startSpinner, stopSpinner } from './spinner'; @@ -172,4 +172,43 @@ async function installRls(): Promise { } stopSpinner('RLS components installed successfully'); -} \ No newline at end of file +} + +/** + * Parses given output of `rustup show` and retrieves the local active toolchain. + */ +export function parseActiveToolchain(rustupOutput: string): string { + // There may a default entry under 'installed toolchains' section, so search + // for currently active/overridden one only under 'active toolchain' section + const activeToolchainsIndex = rustupOutput.search('active toolchain'); + if (activeToolchainsIndex === -1) { + throw new Error(`couldn't find active toolchains`); + } + + rustupOutput = rustupOutput.substr(activeToolchainsIndex); + + const matchActiveChannel = new RegExp(/^(\S*) \((?:default|overridden)/gm); + const match = matchActiveChannel.exec(rustupOutput); + if (match === null) { + throw new Error(`couldn't find active toolchain under 'active toolchains'`); + } else if (match.length > 2) { + throw new Error(`multiple active toolchains found under 'active toolchains'`); + } + + return match[1]; +} + +/** + * Returns active (including local overrides) toolchain, as specified by rustup. + * May throw if rustup at specified path can't be executed. + */ +export function getActiveChannel(rustupPath: string, cwd = workspace.rootPath): string { + // rustup info might differ depending on where it's executed + // (e.g. when a toolchain is locally overriden), so executing it + // under our current workspace root should give us close enough result + const output = child_process.execSync(`${rustupPath} show`, {cwd: cwd}).toString(); + + const activeChannel = parseActiveToolchain(output); + console.info(`Detected active channel: ${activeChannel} (since 'rust-client.channel' is unspecified)`); + return activeChannel; +}