From 88bf2ff5e3c86e9547eec3bb8941dfe2014a5aa7 Mon Sep 17 00:00:00 2001 From: Kalita Alexey Date: Thu, 25 May 2017 18:26:37 +0300 Subject: [PATCH] Ask user to choose mode --- package.json | 11 +- src/components/configuration/Configuration.ts | 66 +++-- src/components/configuration/Rustup.ts | 8 + src/extension.ts | 257 ++++++++++-------- 4 files changed, 201 insertions(+), 141 deletions(-) diff --git a/package.json b/package.json index 751aabc..8e87a59 100644 --- a/package.json +++ b/package.json @@ -165,13 +165,18 @@ "title": "Rust extension configuration", "type": "object", "properties": { - "rust.forceLegacyMode": { + "rust.mode": { "type": [ - "boolean", + "string", "null" ], "default": null, - "description": "Flag indicating if the extension shouldn't try to run RLS. By default, it is false" + "description": "The mode in which the extension will function", + "enum": [ + "legacy", + "rls", + null + ] }, "rust.racerPath": { "type": [ diff --git a/src/components/configuration/Configuration.ts b/src/components/configuration/Configuration.ts index b3cf37a..e61f016 100644 --- a/src/components/configuration/Configuration.ts +++ b/src/components/configuration/Configuration.ts @@ -35,8 +35,25 @@ export enum Mode { RLS } +/** + * Returns the representation of the specified mode suitable for being a value for the + * configuration parameter + * @param mode The mode which representation will be returned for + * @return The representation of the specified mode + */ +export function asConfigurationParameterValue(mode: Mode | undefined): string | null { + switch (mode) { + case Mode.Legacy: + return 'legacy'; + case Mode.RLS: + return 'rls'; + case undefined: + return null; + } +} + namespace Properties { - export const forceLegacyMode = 'forceLegacyMode'; + export const mode = 'mode'; } /** @@ -45,9 +62,7 @@ namespace Properties { */ export class Configuration { private _mode: Mode | undefined; - private _isForcedLegacyMode: boolean; private logger: ChildLogger; - private rustInstallation: Rustup | NotRustup | undefined; /** @@ -99,7 +114,7 @@ export class Configuration { undefined, undefined ); - if (!configuration.isForcedLegacyMode()) { + if (configuration._mode === Mode.RLS) { await configuration.updatePathToRlsExecutableSpecifiedByUser(); } return configuration; @@ -144,20 +159,26 @@ export class Configuration { ); } + /** + * Returns the mode which the extension runs in + * @return The mode + */ public mode(): Mode | undefined { return this._mode; } + /** + * Saves the specified mode in both the object and the configuration + * @param mode The mode + */ public setMode(mode: Mode): void { if (this._mode !== undefined) { - this.logger.createChildLogger(`setMode(${mode}): `).error('this._mode !== undefined. The method should not have been called'); + this.logger.error(`setMode(${mode}): this._mode(${this._mode}) !== undefined. The method should not have been called`); return; } this._mode = mode; - } - - public isForcedLegacyMode(): boolean { - return this._isForcedLegacyMode; + const configuration = Configuration.getConfiguration(); + configuration.update(Properties.mode, asConfigurationParameterValue(mode), true); } /** @@ -368,12 +389,6 @@ export class Configuration { } } - public setForceLegacyMode(value: boolean): void { - const configuration = Configuration.getConfiguration(); - configuration.update(Properties.forceLegacyMode, value, true); - this._isForcedLegacyMode = value; - } - private static async loadRustcSysRoot(): Promise { const executable = 'rustc'; @@ -478,18 +493,23 @@ export class Configuration { rlsPathSpecifiedByUser: string | undefined, pathToRacer: string | undefined ) { - function isForcedLegacyMode(): boolean { + function mode(): Mode | undefined { const configuration = Configuration.getConfiguration(); - const value: boolean | null | undefined = configuration[Properties.forceLegacyMode]; - if (value) { - // It is actually `true`, but who knows how the code would behave later - return value; + const value: string | null | undefined = configuration[Properties.mode]; + if (typeof value === 'string') { + switch (value) { + case asConfigurationParameterValue(Mode.Legacy): + return Mode.Legacy; + case asConfigurationParameterValue(Mode.RLS): + return Mode.RLS; + default: + return undefined; + } } else { - return false; + return undefined; } } - this._mode = undefined; - this._isForcedLegacyMode = isForcedLegacyMode(); + this._mode = mode(); this.logger = logger; this.rustInstallation = rustInstallation; this.pathToRustSourceCodeSpecifiedByUser = pathToRustSourceCodeSpecifiedByUser; diff --git a/src/components/configuration/Rustup.ts b/src/components/configuration/Rustup.ts index dd38849..3494800 100644 --- a/src/components/configuration/Rustup.ts +++ b/src/components/configuration/Rustup.ts @@ -272,6 +272,14 @@ export class Rustup { return this.isComponentInstalled(Rustup.getRlsComponentName()); } + /** + * Returns whether "rust-analysis" is installed + * @return The flag indicating whether "rust-analysis" is installed + */ + public isRustAnalysisInstalled(): boolean { + return this.isComponentInstalled(Rustup.getRustAnalysisComponentName()); + } + /** * Returns true if the component `rust-analysis` can be installed otherwise false. * If the component is already installed, the method returns false diff --git a/src/extension.ts b/src/extension.ts index d5ae274..7ad0ba4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,57 +21,43 @@ import RootLogger from './components/logging/root_logger'; import LegacyModeManager from './legacy_mode_manager'; -enum RlsInstallDecision { - DoNotAskAgain, - Install, - NotInstall -} - /** - * Asks the user's permission to install RLS - * @param logger The logger - * @return The promise which after resolving contains true if the user agreed otherwise false + * Asks the user to choose a mode which the extension will run in. + * It is possible that the user will decline choosing and in that case the extension will run in + * Legacy Mode + * @return The promise which is resolved with either the chosen mode by the user or undefined */ -async function askPermissionToInstallRls(logger: RootLogger): Promise { - const functionLogger = logger.createChildLogger('askPermissionToInstallRls: '); - const readAboutRlsChoice = 'Read about RLS'; - const installRlsChoice = 'Install RLS'; - const doNotAskMeAgainChoice = 'Don\'t ask me again'; - // Asking the user if the user wants to install RLS until the user declines or agrees. - // A user can decide to install RLS, then we install it. - // A user can decide to read about RLS, then we open a link to the repository of RLS and ask again after +async function askUserToChooseMode(): Promise { + const message = 'Choose a mode in which the extension will function'; + const rlsChoice = 'RLS'; + const legacyChoice = 'Legacy'; + const readAboutChoice = 'Read about modes'; while (true) { - const choice: string | undefined = await window.showInformationMessage( - 'You use Rustup, but RLS was not found. RLS provides a good user experience', - readAboutRlsChoice, - installRlsChoice, - doNotAskMeAgainChoice - ); - functionLogger.debug(`choice=${choice}`); + const choice = await window.showInformationMessage(message, rlsChoice, legacyChoice, + readAboutChoice); switch (choice) { - case readAboutRlsChoice: - open('https://github.com/rust-lang-nursery/rls'); + case rlsChoice: + return Mode.RLS; + case legacyChoice: + return Mode.Legacy; + case readAboutChoice: + open('https://github.com/editor-rs/vscode-rust/blob/master/doc/main.md'); break; - case installRlsChoice: - return RlsInstallDecision.Install; - case doNotAskMeAgainChoice: - return RlsInstallDecision.DoNotAskAgain; default: - return RlsInstallDecision.NotInstall; + return undefined; } } } /** - * Asks the user's permission to install the nightly toolchain - * @param logger The logger to log messages - * @return true if the user granted the permission otherwise false + * Asks the user's permission to install something + * @param what What to install + * @return The flag indicating whether the user gave the permission */ -async function askPermissionToInstallNightlyToolchain(logger: ChildLogger): Promise { - const functionLogger = logger.createChildLogger('askPermissionToInstallNightlyToolchain: '); +async function askPermissionToInstall(what: string): Promise { const installChoice = 'Install'; - const choice = await window.showInformationMessage('Do you want to install the nightly toolchain?', installChoice); - functionLogger.debug(`choice=${choice}`); + const message = `It seems ${what} is not installed. Do you want to install it?`; + const choice = await window.showInformationMessage(message, installChoice); return choice === installChoice; } @@ -83,14 +69,14 @@ async function askPermissionToInstallNightlyToolchain(logger: ChildLogger): Prom async function handleMissingNightlyToolchain(logger: ChildLogger, rustup: Rustup): Promise { const functionLogger = logger.createChildLogger('handleMissingNightlyToolchain: '); await window.showInformationMessage('The nightly toolchain is not installed, but is required to install RLS'); - const permissionGranted = await askPermissionToInstallNightlyToolchain(logger); - functionLogger.debug(`permissionGranted=${permissionGranted}`); + const permissionGranted = await askPermissionToInstall('the nightly toolchain'); + functionLogger.debug(`permissionGranted= ${permissionGranted}`); if (!permissionGranted) { return false; } window.showInformationMessage('The nightly toolchain is being installed. It can take a while. Please be patient'); const toolchainInstalled = await rustup.installToolchain('nightly'); - functionLogger.debug(`toolchainInstalled=${toolchainInstalled}`); + functionLogger.debug(`toolchainInstalled= ${toolchainInstalled}`); if (!toolchainInstalled) { return false; } @@ -98,82 +84,121 @@ async function handleMissingNightlyToolchain(logger: ChildLogger, rustup: Rustup return true; } +async function handleMissingRustup(logger: RootLogger, configuration: Configuration): Promise { + const functionLogger = logger.createChildLogger('handleMissingRustup: '); + functionLogger.debug('enter'); + const message = 'You do not use Rustup. Rustup is a preferred way to install Rust and its components'; + const doNotShowMeAgainChoice = 'Don\'t show me this again'; + const choice = await window.showInformationMessage(message, doNotShowMeAgainChoice); + if (choice === doNotShowMeAgainChoice) { + configuration.setMode(Mode.Legacy); + } +} + /** * Handles the case when the user does not have RLS. * It tries to install RLS if it is possible * @param logger The logger to log messages - * @param configuration The configuration + * @param rustup The rustup */ -async function handleMissingRls(logger: RootLogger, configuration: Configuration): Promise { - const functionLogger = logger.createChildLogger('handleMissingRls: '); - const rustup = configuration.getRustInstallation(); - if (!(rustup instanceof Rustup)) { - functionLogger.debug('Rust is either not installed or installed not via Rustup'); - const doNotShowMeAgainChoice = 'Don\'t show me this again'; - const choice: string | undefined = await window.showInformationMessage('You do not use Rustup. Rustup is a preferred way to install Rust and its components', doNotShowMeAgainChoice); - if (choice === doNotShowMeAgainChoice) { - configuration.setForceLegacyMode(true); +async function handleMissingRls(logger: RootLogger, rustup: Rustup): Promise { + async function installComponent(componentName: string, installComponent: () => Promise): Promise { + window.showInformationMessage(`${componentName} is being installed.It can take a while`); + const componentInstalled = await installComponent(); + functionLogger.debug(`${componentName} has been installed= ${componentInstalled} `); + if (componentInstalled) { + window.showInformationMessage(`${componentName} has been installed successfully`); + } else { + window.showErrorMessage(`${componentName} has not been installed.Check the output channel "Rust Logging"`); } - return; + return componentInstalled; } - const rlsInstallDecision: RlsInstallDecision = await askPermissionToInstallRls(logger); - functionLogger.debug(`rlsInstallDecision=${rlsInstallDecision}`); - switch (rlsInstallDecision) { - case RlsInstallDecision.DoNotAskAgain: - configuration.setForceLegacyMode(true); - return; - case RlsInstallDecision.Install: - break; - case RlsInstallDecision.NotInstall: - return; + const functionLogger = logger.createChildLogger('handleMissingRls: '); + if (await askPermissionToInstall('RLS')) { + functionLogger.debug('Permission to install RLS has been granted'); + } else { + functionLogger.debug('Permission to install RLS has not granted'); + return false; } if (!rustup.isNightlyToolchainInstalled()) { + functionLogger.debug('The nightly toolchain is not installed'); await handleMissingNightlyToolchain(functionLogger, rustup); if (!rustup.isNightlyToolchainInstalled()) { - functionLogger.error('nightly toolchain is not installed'); - return; - } - } - async function installComponent(componentName: string, installComponent: () => Promise): Promise { - window.showInformationMessage(`${componentName} is being installed. It can take a while`); - const componentInstalled: boolean = await installComponent(); - functionLogger.debug(`${componentName} has been installed=${componentInstalled}`); - if (componentInstalled) { - window.showInformationMessage(`${componentName} has been installed successfully`); - } else { - window.showErrorMessage(`${componentName} has not been installed. Check the output channel "Rust Logging"`); + functionLogger.debug('The nightly toolchain is not installed'); + return false; } - return componentInstalled; } - const rlsCanBeInstalled: boolean = rustup.canInstallRls(); - functionLogger.debug(`rlsCanBeInstalled=${rlsCanBeInstalled}`); - if (!rlsCanBeInstalled) { - return; + if (rustup.canInstallRls()) { + functionLogger.debug('RLS can be installed'); + } else { + functionLogger.error('RLS cannot be installed'); + return false; } - const rlsInstalled: boolean = await installComponent( + const rlsInstalled = await installComponent( 'RLS', async () => { return await rustup.installRls(); } ); - if (!rlsInstalled) { - return; + if (rlsInstalled) { + functionLogger.debug('RLS has been installed'); + } else { + functionLogger.error('RLS has not been installed'); + return false; } - const rustAnalysisCanBeInstalled: boolean = rustup.canInstallRustAnalysis(); - functionLogger.debug(`rustAnalysisCanBeInstalled=${rustAnalysisCanBeInstalled}`); - if (!rustAnalysisCanBeInstalled) { - return; + if (rustup.isRustAnalysisInstalled()) { + functionLogger.debug('rust-analysis is installed'); + } else if (rustup.canInstallRustAnalysis()) { + functionLogger.debug('rust-analysis can be installed'); + } else { + functionLogger.error('rust-analysis cannot be installed'); + return false; } - await installComponent( + return await installComponent( 'rust-analysis', async () => { return await rustup.installRustAnalysis(); } ); } +export async function handleChoosingRlsMode(logger: RootLogger, configuration: Configuration, + rustup: Rustup): Promise { + let canSetMode = false; + if (configuration.getPathToRlsExecutable() === undefined) { + if (await handleMissingRls(logger, rustup)) { + canSetMode = true; + } + } else { + canSetMode = true; + } + if (canSetMode) { + configuration.setMode(Mode.RLS); + } +} + export async function activate(ctx: ExtensionContext): Promise { const loggingManager = new LoggingManager(); const logger = loggingManager.getLogger(); const configuration = await Configuration.create(logger.createChildLogger('Configuration: ')); - if (!configuration.getPathToRlsExecutable() && !configuration.isForcedLegacyMode()) { - await handleMissingRls(logger, configuration); + if (configuration.mode() === undefined) { + // The current configuration does not contain any specified mode and hence we should try to + // choose one. + const rustInstallation = configuration.getRustInstallation(); + if (rustInstallation instanceof Rustup) { + const mode = await askUserToChooseMode(); + switch (mode) { + case Mode.Legacy: + configuration.setMode(Mode.Legacy); + break; + case Mode.RLS: + handleChoosingRlsMode(logger, configuration, rustInstallation); + if (configuration.getPathToRlsExecutable() === undefined) { + await handleMissingRls(logger, rustInstallation); + } + break; + case undefined: + break; + } + } else { + await handleMissingRustup(logger, configuration); + } } const currentWorkingDirectoryManager = new CurrentWorkingDirectoryManager(); const cargoManager = new CargoManager( @@ -186,27 +211,36 @@ export async function activate(ctx: ExtensionContext): Promise { addExecutingActionOnSave(ctx, configuration, cargoManager); } +async function runInLegacyMode(context: ExtensionContext, configuration: Configuration, + currentWorkingDirectoryManager: CurrentWorkingDirectoryManager, + logger: RootLogger): Promise { + const legacyModeManager = await LegacyModeManager.create( + context, + configuration, + currentWorkingDirectoryManager, + logger.createChildLogger('Legacy Mode Manager: ') + ); + await legacyModeManager.start(); +} + /** * Starts the extension in RLS mode * @param context An extension context to use * @param logger A logger to log messages * @param configuration A configuration - * @param pathToRlsExecutable A path to the executable of RLS */ -function runInRlsMode( - context: ExtensionContext, - logger: RootLogger, - configuration: Configuration, - rlsPath: string -): void { +function runInRlsMode(context: ExtensionContext, logger: RootLogger, + configuration: Configuration): void { const functionLogger = logger.createChildLogger('runInRlsMode: '); - functionLogger.debug(`rlsPath=${rlsPath}`); + // This method is called only when RLS's path is defined, so we don't have to check it again + const rlsPath = configuration.getPathToRlsExecutable(); + functionLogger.debug(`rlsPath= ${rlsPath} `); const env = configuration.getRlsEnv(); - functionLogger.debug(`env=${JSON.stringify(env)}`); + functionLogger.debug(`env= ${JSON.stringify(env)} `); const args = configuration.getRlsArgs(); - functionLogger.debug(`args=${JSON.stringify(args)}`); + functionLogger.debug(`args= ${JSON.stringify(args)} `); const revealOutputChannelOn = configuration.getRlsRevealOutputChannelOn(); - functionLogger.debug(`revealOutputChannelOn=${revealOutputChannelOn}`); + functionLogger.debug(`revealOutputChannelOn= ${revealOutputChannelOn} `); const languageClientManager = new LanguageClientManager( context, logger.createChildLogger('Language Client Manager: '), @@ -224,21 +258,14 @@ async function chooseModeAndRun( configuration: Configuration, currentWorkingDirectoryManager: CurrentWorkingDirectoryManager ): Promise { - const rls: string | undefined = configuration.getPathToRlsExecutable(); - const isLegacyMode = configuration.isForcedLegacyMode() || !rls; - if (isLegacyMode) { - configuration.setMode(Mode.Legacy); - const legacyModeManager = await LegacyModeManager.create( - context, - configuration, - currentWorkingDirectoryManager, - logger.createChildLogger('Legacy Mode Manager: ') - ); - - await legacyModeManager.start(); - } else { - configuration.setMode(Mode.RLS); - runInRlsMode(context, logger, configuration, rls); + switch (configuration.mode()) { + case Mode.Legacy: + case undefined: + await runInLegacyMode(context, configuration, currentWorkingDirectoryManager, logger); + break; + case Mode.RLS: + runInRlsMode(context, logger, configuration); + break; } }