diff --git a/doc/cargo_command_execution.md b/doc/cargo_command_execution.md
index b9aea67..09168a8 100644
--- a/doc/cargo_command_execution.md
+++ b/doc/cargo_command_execution.md
@@ -19,7 +19,7 @@ These commands are available through the command palette (Ctrl+S
## Execute Command On Save
-The extension supports executing some of these commands after saving the active document.
+The extension supports executing some of these commands after saving the active document.
The `"rust.actionOnSave"` configuration parameter specifies which command to execute.
@@ -87,6 +87,27 @@ The possible values are:
"rust.cargoEnv": { "RUST_BACKTRACE": 1 }
```
+### Executing Cargo commands in an integrated terminal
+
+The `"rust.executeCargoCommandInTerminal"` configuration parameter controls whether a Cargo command should be executed in an integrated terminal.
+
+By default, the extension executes Cargo commands as child processes. It then parses the output of the command and publishes diagnostics. Executing Cargo commands in an integrated terminal is useful if you need to run a binary and enter some text.
+
+Unfortunately, there is currently no way to parse output of an integrated terminal. This means diagnostics cannot be shown in the editor.
+
+The configuration parameter supports the following values:
+
+* `true` - A Cargo command should be executed in an integrated terminal.
+* `false` - A Cargo command should be executed as a child process.
+
+### Specifying what kind of integrated terminal is used
+
+The `"rust.shell.kind.windows"` configuration parameter specifies what kind of integrated terminal is used.
+
+The configuration parameter should be specified only if the user uses Windows with [WSL](https://msdn.microsoft.com/en-us/commandline/wsl/install_guide).
+
+In all other cases the extension should be able to determine it itself.
+
### Setting An Action To Handle Starting A New Command If There Is Another Command Running
The `"rust.actionOnStartingCommandIfThereIsRunningCommand"` configuration parameter specifies what the extension should do in case of starting a new command if there is a previous command running.
diff --git a/doc/legacy_mode/main.md b/doc/legacy_mode/main.md
index 6c2e810..b0eebe7 100644
--- a/doc/legacy_mode/main.md
+++ b/doc/legacy_mode/main.md
@@ -47,16 +47,3 @@ The output channel will not be shown under any of the following conditions:
- `"rust.executeCargoCommandInTerminal"` is set to `true`
- `"rust.actionOnSave"` is set to `"check"`
-
-### Executing Cargo commands in an integrated terminal
-
-The `"rust.executeCargoCommandInTerminal"` configuration parameter controls whether [a Cargo command should be executed](../cargo_command_execution.md) in an integrated terminal.
-
-By default, the extension executes Cargo commands as child processes. It then parses the output of the command and publishes diagnostics. Executing Cargo commands in an integrated terminal is useful if you need to run a binary and enter some text.
-
-Unfortunately, there is currently no way to parse output of an integrated terminal. This means diagnostics cannot be shown in the editor.
-
-The configuration parameter supports the following values:
-
-* `true` - A Cargo command should be executed in an integrated terminal.
-* `false` - A Cargo command should be executed as a child process.
diff --git a/package.json b/package.json
index 9d361e8..e8a1c53 100644
--- a/package.json
+++ b/package.json
@@ -536,6 +536,20 @@
]
}
}
+ },
+ "rust.shell.kind.windows": {
+ "default": "null",
+ "enum": [
+ "cmd",
+ "powershell",
+ "shell",
+ "wsl",
+ "null"
+ ],
+ "type": [
+ "string",
+ "null"
+ ]
}
}
}
diff --git a/src/CommandLine.ts b/src/CommandLine.ts
index 3680fb1..4ab12e1 100644
--- a/src/CommandLine.ts
+++ b/src/CommandLine.ts
@@ -1,24 +1,5 @@
-export enum Shell {
- PowerShell,
- CMD,
- Shell
-}
-
-/**
- * Parses the specified string as a variant of the enum `Shell`.
- * If it fails to match the string, it returns `Shell.Shell`
- * @param shell The shell textual representation
- * @return The shell matching the specified string
- */
-export function parseShell(shell: string): Shell {
- if (shell.includes('powershell')) {
- return Shell.PowerShell;
- }
- if (shell.includes('cmd')) {
- return Shell.CMD;
- }
- return Shell.Shell;
-}
+import { Shell } from './Shell';
+import { correctPath } from './WslShellUtils';
/**
* Creates a command to set the environment variable
@@ -32,9 +13,10 @@ export function getCommandToSetEnvVar(shell: Shell, varName: string, varValue: s
switch (shell) {
case Shell.PowerShell:
return `$ENV:${varName}="${varValue}"`;
- case Shell.CMD:
+ case Shell.Cmd:
return `set ${varName}=${varValue}`;
case Shell.Shell:
+ case Shell.Wsl:
return ` export ${varName}=${varValue}`;
}
}
@@ -55,7 +37,7 @@ export function escapeSpaces(s: string, shell: Shell): string {
s = s.replace(new RegExp('` ', 'g'), ' ');
// Escape
return s.replace(new RegExp(' ', 'g'), '` ');
- case Shell.CMD:
+ case Shell.Cmd:
s = s.concat();
if (!s.startsWith('"')) {
s = '"'.concat(s);
@@ -65,6 +47,7 @@ export function escapeSpaces(s: string, shell: Shell): string {
}
return s;
case Shell.Shell:
+ case Shell.Wsl:
s = s.concat();
if (!s.startsWith('\'')) {
s = '\''.concat(s);
@@ -76,6 +59,16 @@ export function escapeSpaces(s: string, shell: Shell): string {
}
}
+export function getCommandToChangeWorkingDirectory(
+ shell: Shell,
+ workingDirectory: string
+): string {
+ if (shell === Shell.Wsl) {
+ workingDirectory = correctPath(workingDirectory);
+ }
+ return getCommandForArgs(shell, ['cd', workingDirectory]);
+}
+
/**
* Prepares the specified arguments to be passed to the specified shell and constructs the command
* from the arguments
@@ -95,28 +88,26 @@ export function getCommandForArgs(shell: Shell, args: string[]): string {
* @return A created command which if it is passed to a terminal,
* it will execute the statements
*/
-export function getCommandToExecuteStatementsOneByOneIfPreviousIsSucceed(shell: Shell, statements: string[]): string {
+export function getCommandToExecuteStatementsOneByOneIfPreviousIsSucceed(
+ shell: Shell,
+ statements: string[]
+): string {
if (statements.length === 0) {
return '';
}
-
- if (process.platform === 'win32' && shell === Shell.PowerShell) {
+ if (shell === Shell.PowerShell) {
let command = statements[0];
-
for (let i = 1; i < statements.length; ++i) {
command += `; if ($?) { ${statements[i]}; }`;
}
-
return command;
} else {
// The string starts with space to make sh not save the command.
// This code is also executed for cmd on Windows, but leading space doesn't break anything
let command = ' ' + statements[0];
-
for (let i = 1; i < statements.length; ++i) {
command += ` && ${statements[i]}`;
}
-
return command;
}
}
diff --git a/src/ConfigurationParameter.ts b/src/ConfigurationParameter.ts
new file mode 100644
index 0000000..ad8323c
--- /dev/null
+++ b/src/ConfigurationParameter.ts
@@ -0,0 +1,25 @@
+import { WorkspaceConfiguration, workspace } from 'vscode';
+
+/**
+ * The main goal of the class is to store the parameter's name and to expose functions to get/set
+ * the value of the parameter
+ */
+export class ConfigurationParameter {
+ private _parameterName: string;
+
+ public constructor(parameterName: string) {
+ this._parameterName = parameterName;
+ }
+
+ public getValue(): any {
+ return this.getConfiguration().get(this._parameterName);
+ }
+
+ public async setValue(value: any): Promise {
+ await this.getConfiguration().update(this._parameterName, value, true);
+ }
+
+ private getConfiguration(): WorkspaceConfiguration {
+ return workspace.getConfiguration('');
+ }
+}
diff --git a/src/IShellProvider.ts b/src/IShellProvider.ts
new file mode 100644
index 0000000..24df172
--- /dev/null
+++ b/src/IShellProvider.ts
@@ -0,0 +1,8 @@
+import { Shell } from './Shell';
+
+/**
+ * The interface declares methods which should be implemented for any shell providers
+ */
+export interface IShellProvider {
+ getValue(): Promise;
+}
diff --git a/src/NotWindowsShellProvider.ts b/src/NotWindowsShellProvider.ts
new file mode 100644
index 0000000..c3c1e44
--- /dev/null
+++ b/src/NotWindowsShellProvider.ts
@@ -0,0 +1,12 @@
+import { IShellProvider } from './IShellProvider';
+import { Shell } from './Shell';
+
+/**
+ * The main goal of the class is to provide the current value of the shell
+ */
+export class NotWindowsShellProvider implements IShellProvider {
+ public getValue(): Promise {
+ // All OS expect Windows use Shell
+ return Promise.resolve(Shell.Shell);
+ }
+}
diff --git a/src/Shell.ts b/src/Shell.ts
new file mode 100644
index 0000000..b144cc4
--- /dev/null
+++ b/src/Shell.ts
@@ -0,0 +1,51 @@
+/**
+ * A enumeration of possible shells
+ */
+export enum Shell {
+ PowerShell,
+ Cmd,
+ Shell,
+ Wsl
+}
+
+/**
+ * The list of all shell values
+ */
+export const VALUES = [Shell.PowerShell, Shell.Cmd, Shell.Shell, Shell.Wsl];
+
+/**
+ * The list of textual forms of all shell values
+ */
+export const VALUE_STRINGS = VALUES.map(toString);
+
+export function fromString(s: string): Shell | undefined {
+ switch (s) {
+ case 'powershell':
+ return Shell.PowerShell;
+ case 'cmd':
+ return Shell.Cmd;
+ case 'shell':
+ return Shell.Shell;
+ case 'wsl':
+ return Shell.Wsl;
+ default:
+ return undefined;
+ }
+}
+
+/**
+ * Returns the textual form of the specified shell
+ * @param shell The shell to convert to string
+ */
+export function toString(shell: Shell): string {
+ switch (shell) {
+ case Shell.PowerShell:
+ return 'powershell';
+ case Shell.Cmd:
+ return 'cmd';
+ case Shell.Shell:
+ return 'shell';
+ case Shell.Wsl:
+ return 'wsl';
+ }
+}
diff --git a/src/ShellProviderManager.ts b/src/ShellProviderManager.ts
new file mode 100644
index 0000000..a287c37
--- /dev/null
+++ b/src/ShellProviderManager.ts
@@ -0,0 +1,32 @@
+import { ILogger } from './components/logging/ILogger';
+import { IShellProvider } from './IShellProvider';
+import { Shell } from './Shell';
+import { NotWindowsShellProvider } from './NotWindowsShellProvider';
+import { WindowsShellProvider } from './WindowsShellProvider';
+
+/**
+ * The main goal of the class is to provide the current value of the shell
+ */
+export class ShellProviderManager {
+ private _shellProvider: IShellProvider;
+
+ /**
+ * Creates a new object which can be used to get the current value of the shell
+ * @param logger The logger which is used to create child logger which will be used to log
+ * messages
+ */
+ public constructor(logger: ILogger) {
+ if (process.platform === 'win32') {
+ this._shellProvider = new WindowsShellProvider(logger);
+ } else {
+ this._shellProvider = new NotWindowsShellProvider();
+ }
+ }
+
+ /**
+ * Gets the current value of the shell and returns it
+ */
+ public async getValue(): Promise {
+ return await this._shellProvider.getValue();
+ }
+}
diff --git a/src/WindowsShellProvider.ts b/src/WindowsShellProvider.ts
new file mode 100644
index 0000000..a673872
--- /dev/null
+++ b/src/WindowsShellProvider.ts
@@ -0,0 +1,157 @@
+import { window } from 'vscode';
+import { ILogger } from './components/logging/ILogger';
+import { ConfigurationParameter } from './ConfigurationParameter';
+import { IShellProvider } from './IShellProvider';
+import { Shell, fromString, toString, VALUE_STRINGS } from './Shell';
+
+/**
+ * The main goal of the class is to provide the current value of the shell.
+ * There are three sources which the class can use to determine the current value of the shell.
+ * * From the configuration parameter `rust.shell.kind.windows`
+ * * From the configuration parameter `terminal.integrated.shell.windows`
+ * * Asking the user to choose any of possible shell values
+ */
+export class WindowsShellProvider implements IShellProvider {
+ private _logger: ILogger;
+ private _specialConfigurationParameter: ConfigurationParameter;
+ private _gettingValueFromSpecialConfigurationParameter: GettingValueFromSpecialConfigurationParameter;
+ private _determiningValueFromTerminalExecutable: DeterminingValueFromTerminalExecutable;
+ private _askingUserToChooseValue: AskingUserToChooseValue;
+
+ /**
+ * Creates a new object which can be used to get the current value of the shell
+ * @param logger The logger which is used to create child logger which will be used to log
+ * messages
+ */
+ public constructor(logger: ILogger) {
+ this._logger = logger.createChildLogger('WindowsShellProvider: ');
+ this._specialConfigurationParameter = new ConfigurationParameter('rust.shell.kind.windows');
+ this._gettingValueFromSpecialConfigurationParameter = new GettingValueFromSpecialConfigurationParameter(this._specialConfigurationParameter);
+ this._determiningValueFromTerminalExecutable = new DeterminingValueFromTerminalExecutable(
+ new ConfigurationParameter('terminal.integrated.shell.windows')
+ );
+ this._askingUserToChooseValue = new AskingUserToChooseValue(logger);
+ }
+
+ /**
+ * Gets the current value of the shell and returns it. This function is asynchronous because
+ * it can ask the user to choose some value
+ */
+ public async getValue(): Promise {
+ const logger = this._logger.createChildLogger('getValue: ');
+ const configValue = this._gettingValueFromSpecialConfigurationParameter.getValue();
+ if (configValue !== undefined) {
+ logger.debug(`configValue=${configValue}`);
+ return configValue;
+ }
+ const determinedValue = this._determiningValueFromTerminalExecutable.determineValue();
+ if (determinedValue !== undefined) {
+ logger.debug(`determinedValue=${determinedValue}`);
+ return determinedValue;
+ }
+ const userValue = await this._askingUserToChooseValue.askUser();
+ if (userValue !== undefined) {
+ logger.debug(`userValue=${toString(userValue)}`);
+ // The user has chosen some value. We need to save it to the special configuration
+ // parameter to avoid asking the user in the future
+ this._specialConfigurationParameter.setValue(toString(userValue));
+ return userValue;
+ }
+ return undefined;
+ }
+}
+
+/**
+ * The main goal of the class is to provide the current value of the shell from the configuration
+ * parameter `rust.shell.kind.*`
+ */
+class GettingValueFromSpecialConfigurationParameter {
+ private _parameter: ConfigurationParameter;
+
+ /**
+ * Creates a new object which can be used to get the current value of the shell
+ * @param parameter The configuration parameter
+ */
+ public constructor(parameter: ConfigurationParameter) {
+ this._parameter = parameter;
+ }
+
+ /**
+ * Gets the current value of the shell from the configuration parameter and returns it
+ * @return if the configuration parameter contains some valid value, the value, `undefined`
+ * otherwise
+ */
+ public getValue(): Shell | undefined {
+ const kind = this._parameter.getValue();
+ if (typeof kind === 'string') {
+ return fromString(kind);
+ }
+ return undefined;
+ }
+}
+
+/**
+ * The main goal of the class is to provide the current value of the shell which is determined
+ * from the configuration parameter `terminal.integrated.shell.*`
+ */
+class DeterminingValueFromTerminalExecutable {
+ private _parameter: ConfigurationParameter;
+
+ /**
+ * Creates a new object which can be to get the current value of the shell
+ * @param parameter The configuration parameter
+ */
+ public constructor(parameter: ConfigurationParameter) {
+ this._parameter = parameter;
+ }
+
+ /**
+ * Determines the current value of the shell and returns it
+ * @return if some value is determined, the value, `undefined` otherwise
+ */
+ public determineValue(): Shell | undefined {
+ const shellPath = this._parameter.getValue();
+ const defaultValue = undefined;
+ if (!shellPath) {
+ return defaultValue;
+ }
+ if (shellPath.includes('powershell')) {
+ return Shell.PowerShell;
+ }
+ if (shellPath.includes('cmd')) {
+ return Shell.Cmd;
+ }
+ return defaultValue;
+ }
+}
+
+/**
+ * The main goal of the class is to ask the user to choose some shell
+ */
+class AskingUserToChooseValue {
+ private _logger: ILogger;
+
+ /**
+ * Creates a new object which can be used to ask the user to choose some shell
+ * @param logger The logger to log messages
+ */
+ public constructor(logger: ILogger) {
+ this._logger = logger;
+ }
+
+ public async askUser(): Promise {
+ const logger = this._logger.createChildLogger('askUser: ');
+ await window.showInformationMessage('In order to run a command in the integrated terminal, the kind of shell should be chosen');
+ const choice = await window.showQuickPick(VALUE_STRINGS);
+ if (!choice) {
+ logger.debug('the user has dismissed the quick pick');
+ return undefined;
+ }
+ const shell = fromString(choice);
+ if (shell === undefined) {
+ logger.debug(`the user has chosen some impossible value; choice=${choice}`);
+ return undefined;
+ }
+ return shell;
+ }
+}
diff --git a/src/WslShellUtils.ts b/src/WslShellUtils.ts
new file mode 100644
index 0000000..2cebf1b
--- /dev/null
+++ b/src/WslShellUtils.ts
@@ -0,0 +1,11 @@
+/**
+ * WSL mounts disks to /mnt/. The path which can be passed to the function looks like
+ * C:\Directory. For the path the function will return /mnt/c/Directory
+ * @param path The path to convert
+ */
+export function correctPath(path: string): string {
+ const disk = path.substr(0, 1).toLowerCase(); // For `C:\\Directory` it will be `C`
+ path = path.replace(new RegExp('\\\\', 'g'), '/'); // After the line it will look like `C:/Directory`
+ const pathWithoutDisk = path.substring(path.indexOf('/') + 1); // For `C:/Directory` it will be `Directory`
+ return `/mnt/${disk}/${pathWithoutDisk}`;
+}
diff --git a/src/components/cargo/CargoManager.ts b/src/components/cargo/CargoManager.ts
index 6187beb..a607d45 100644
--- a/src/components/cargo/CargoManager.ts
+++ b/src/components/cargo/CargoManager.ts
@@ -1,6 +1,7 @@
import * as tmp from 'tmp';
import { Disposable, ExtensionContext, Uri, commands, window, workspace } from 'vscode';
import { CargoInvocationManager } from '../../CargoInvocationManager';
+import { ShellProviderManager } from '../../ShellProviderManager';
import { Configuration } from '../configuration/Configuration';
import { CurrentWorkingDirectoryManager }
from '../configuration/current_working_directory_manager';
@@ -20,6 +21,7 @@ export class CargoManager {
configuration: Configuration,
cargoInvocationManager: CargoInvocationManager,
currentWorkingDirectoryManager: CurrentWorkingDirectoryManager,
+ shellProviderManager: ShellProviderManager,
logger: ChildLogger
) {
const stopCommandName = 'rust.cargo.terminate';
@@ -28,6 +30,7 @@ export class CargoManager {
configuration,
cargoInvocationManager,
currentWorkingDirectoryManager,
+ shellProviderManager,
logger.createChildLogger('CargoTaskManager: '),
stopCommandName
);
diff --git a/src/components/cargo/CargoTaskManager.ts b/src/components/cargo/CargoTaskManager.ts
index 801d0a5..d35a3da 100644
--- a/src/components/cargo/CargoTaskManager.ts
+++ b/src/components/cargo/CargoTaskManager.ts
@@ -1,6 +1,7 @@
import { join } from 'path';
import { ExtensionContext, window } from 'vscode';
import { CargoInvocationManager } from '../../CargoInvocationManager';
+import { ShellProviderManager } from '../../ShellProviderManager';
import { Configuration } from '../configuration/Configuration';
import { CurrentWorkingDirectoryManager }
from '../configuration/current_working_directory_manager';
@@ -25,6 +26,7 @@ export class CargoTaskManager {
configuration: Configuration,
cargoInvocationManager: CargoInvocationManager,
currentWorkingDirectoryManager: CurrentWorkingDirectoryManager,
+ shellProviderManager: ShellProviderManager,
logger: ChildLogger,
stopCommandName: string
) {
@@ -37,7 +39,11 @@ export class CargoTaskManager {
logger.createChildLogger('OutputChannelTaskManager: '),
stopCommandName
);
- this._terminalTaskManager = new TerminalTaskManager(context, configuration);
+ this._terminalTaskManager = new TerminalTaskManager(
+ context,
+ configuration,
+ shellProviderManager
+ );
}
public async invokeCargoInit(crateType: CrateType, name: string, workingDirectory: string): Promise {
diff --git a/src/components/cargo/terminal_task_manager.ts b/src/components/cargo/terminal_task_manager.ts
index 00b0872..80a94de 100644
--- a/src/components/cargo/terminal_task_manager.ts
+++ b/src/components/cargo/terminal_task_manager.ts
@@ -1,14 +1,21 @@
-import { ExtensionContext, Terminal, window, workspace } from 'vscode';
-import { getCommandForArgs, getCommandToSetEnvVar, parseShell }
+import { ExtensionContext, Terminal, window } from 'vscode';
+import { getCommandForArgs, getCommandToChangeWorkingDirectory, getCommandToSetEnvVar }
from '../../CommandLine';
+import { ShellProviderManager } from '../../ShellProviderManager';
import { Configuration } from '../configuration/Configuration';
export class TerminalTaskManager {
private _configuration: Configuration;
private _runningTerminal: Terminal | undefined;
+ private _shellProvider: ShellProviderManager;
- public constructor(context: ExtensionContext, configuration: Configuration) {
+ public constructor(
+ context: ExtensionContext,
+ configuration: Configuration,
+ shellProviderManager: ShellProviderManager
+ ) {
this._configuration = configuration;
+ this._shellProvider = shellProviderManager;
context.subscriptions.push(
window.onDidCloseTerminal(closedTerminal => {
if (closedTerminal === this._runningTerminal) {
@@ -42,7 +49,10 @@ export class TerminalTaskManager {
args = preCommandArgs.concat(command, ...args);
const terminal = window.createTerminal('Cargo Task');
this._runningTerminal = terminal;
- const shell = parseShell(workspace.getConfiguration('terminal')['integrated']['shell']['windows']);
+ const shell = await this._shellProvider.getValue();
+ if (shell === undefined) {
+ return;
+ }
const setEnvironmentVariables = () => {
const cargoEnv = this._configuration.getCargoEnv();
// Set environment variables
@@ -55,7 +65,7 @@ export class TerminalTaskManager {
};
setEnvironmentVariables();
// Change the current directory to a specified directory
- this._runningTerminal.sendText(getCommandForArgs(shell, ['cd', cwd]));
+ this._runningTerminal.sendText(getCommandToChangeWorkingDirectory(shell, cwd));
// Start a requested command
this._runningTerminal.sendText(getCommandForArgs(shell, [executable, ...args]));
this._runningTerminal.show(true);
diff --git a/src/components/logging/ILogger.ts b/src/components/logging/ILogger.ts
new file mode 100644
index 0000000..dde7fbf
--- /dev/null
+++ b/src/components/logging/ILogger.ts
@@ -0,0 +1,11 @@
+import { CapturedMessage } from './captured_message';
+
+export interface ILogger {
+ createChildLogger(loggingMessagePrefix: (() => string) | string): ILogger;
+ debug(message: string): void;
+ error(message: string): void;
+ warning(message: string): void;
+ startMessageCapture(): void;
+ takeCapturedMessages(): CapturedMessage[];
+ stopMessageCaptureAndReleaseCapturedMessages(): void;
+}
diff --git a/src/components/logging/logger.ts b/src/components/logging/logger.ts
index 42d8435..4cce1e9 100644
--- a/src/components/logging/logger.ts
+++ b/src/components/logging/logger.ts
@@ -1,12 +1,15 @@
import { CapturedMessage, CapturedMessageSeverity } from './captured_message';
+import { ILogger } from './ILogger';
-export abstract class Logger {
+export abstract class Logger implements ILogger {
private loggingMessagePrefix: (() => string) | string;
private messageCaptureEnabled: boolean;
private capturedMessages: CapturedMessage[];
+ public abstract createChildLogger(loggingMessagePrefix: (() => string) | string): ILogger;
+
public debug(message: string): void {
const log = this.debugProtected.bind(this);
diff --git a/src/components/tools_installation/installator.ts b/src/components/tools_installation/installator.ts
index a0bb9e2..5c4d39b 100644
--- a/src/components/tools_installation/installator.ts
+++ b/src/components/tools_installation/installator.ts
@@ -1,9 +1,10 @@
import { existsSync } from 'fs';
import * as path from 'path';
-import { ExtensionContext, commands, window, workspace } from 'vscode';
+import { ExtensionContext, commands, window } from 'vscode';
import { CargoInvocationManager } from '../../CargoInvocationManager';
-import { getCommandForArgs, getCommandToExecuteStatementsOneByOneIfPreviousIsSucceed, parseShell }
+import { getCommandForArgs, getCommandToExecuteStatementsOneByOneIfPreviousIsSucceed }
from '../../CommandLine';
+import { ShellProviderManager } from '../../ShellProviderManager';
import { Configuration } from '../configuration/Configuration';
import { ChildLogger } from '../logging/child_logger';
import { MissingToolsStatusBarItem } from './missing_tools_status_bar_item';
@@ -11,6 +12,7 @@ import { MissingToolsStatusBarItem } from './missing_tools_status_bar_item';
export class Installator {
private _configuration: Configuration;
private _cargoInvocationManager: CargoInvocationManager;
+ private _shellProvider: ShellProviderManager;
private _logger: ChildLogger;
private _missingToolsStatusBarItem: MissingToolsStatusBarItem;
private _missingTools: string[];
@@ -19,10 +21,12 @@ export class Installator {
context: ExtensionContext,
configuration: Configuration,
cargoInvocationManager: CargoInvocationManager,
+ shellProviderManager: ShellProviderManager,
logger: ChildLogger
) {
this._configuration = configuration;
this._cargoInvocationManager = cargoInvocationManager;
+ this._shellProvider = shellProviderManager;
this._logger = logger;
const installToolsCommandName = 'rust.install_missing_tools';
this._missingToolsStatusBarItem = new MissingToolsStatusBarItem(context, installToolsCommandName);
@@ -53,12 +57,14 @@ export class Installator {
});
}
- private installMissingTools(): void {
+ private async installMissingTools(): Promise {
const terminal = window.createTerminal('Rust tools installation');
// cargo install tool && cargo install another_tool
const { executable: cargoExecutable, args: cargoArgs } = this._cargoInvocationManager.getExecutableAndArgs();
- const shell = parseShell(workspace.getConfiguration('terminal')['integrated']['shell']['windows']);
-
+ const shell = await this._shellProvider.getValue();
+ if (shell === undefined) {
+ return;
+ }
const statements = this._missingTools.map(tool => {
const args = [cargoExecutable, ...cargoArgs, 'install', tool];
return getCommandForArgs(shell, args);
diff --git a/src/extension.ts b/src/extension.ts
index cfaf0c3..ee72b5f 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -17,6 +17,7 @@ import { RootLogger } from './components/logging/root_logger';
import { CargoInvocationManager } from './CargoInvocationManager';
import { LegacyModeManager } from './legacy_mode_manager';
import * as OutputChannelProcess from './OutputChannelProcess';
+import { ShellProviderManager } from './ShellProviderManager';
import { Toolchain } from './Toolchain';
/**
@@ -503,11 +504,13 @@ export async function activate(ctx: ExtensionContext): Promise {
}
}
const currentWorkingDirectoryManager = new CurrentWorkingDirectoryManager();
+ const shellProviderManager = new ShellProviderManager(logger);
const cargoManager = new CargoManager(
ctx,
configuration,
cargoInvocationManager,
currentWorkingDirectoryManager,
+ shellProviderManager,
logger.createChildLogger('Cargo Manager: ')
);
addExecutingActionOnSave(ctx, configuration, cargoManager);
@@ -536,6 +539,7 @@ export async function activate(ctx: ExtensionContext): Promise {
rustSource,
rustup,
currentWorkingDirectoryManager,
+ shellProviderManager,
logger
);
break;
@@ -551,6 +555,7 @@ async function runInLegacyMode(
rustSource: RustSource,
rustup: Rustup | undefined,
currentWorkingDirectoryManager: CurrentWorkingDirectoryManager,
+ shellProviderManager: ShellProviderManager,
logger: RootLogger
): Promise {
const legacyModeManager = await LegacyModeManager.create(
@@ -560,6 +565,7 @@ async function runInLegacyMode(
rustSource,
rustup,
currentWorkingDirectoryManager,
+ shellProviderManager,
logger.createChildLogger('Legacy Mode Manager: ')
);
await legacyModeManager.start();
diff --git a/src/legacy_mode_manager.ts b/src/legacy_mode_manager.ts
index 902d48d..585d3fb 100644
--- a/src/legacy_mode_manager.ts
+++ b/src/legacy_mode_manager.ts
@@ -14,6 +14,7 @@ import { WorkspaceSymbolProvisionManager }
import { Installator as MissingToolsInstallator }
from './components/tools_installation/installator';
import { CargoInvocationManager } from './CargoInvocationManager';
+import { ShellProviderManager } from './ShellProviderManager';
export class LegacyModeManager {
private context: ExtensionContext;
@@ -31,6 +32,7 @@ export class LegacyModeManager {
rustSource: RustSource,
rustup: Rustup | undefined,
currentWorkingDirectoryManager: CurrentWorkingDirectoryManager,
+ shellProviderManager: ShellProviderManager,
logger: ChildLogger
): Promise {
const formattingManager: FormattingManager | undefined = await FormattingManager.create(context, configuration, logger);
@@ -41,6 +43,7 @@ export class LegacyModeManager {
rustSource,
rustup,
currentWorkingDirectoryManager,
+ shellProviderManager,
logger,
formattingManager
);
@@ -60,6 +63,7 @@ export class LegacyModeManager {
rustSource: RustSource,
rustup: Rustup | undefined,
currentWorkingDirectoryManager: CurrentWorkingDirectoryManager,
+ shellProviderManager: ShellProviderManager,
logger: ChildLogger,
formattingManager: FormattingManager | undefined
) {
@@ -83,6 +87,7 @@ export class LegacyModeManager {
context,
configuration,
cargoInvocationManager,
+ shellProviderManager,
logger.createChildLogger('MissingToolsInstallator: ')
);
}
diff --git a/test/CommandLine.test.ts b/test/CommandLine.test.ts
index 5043373..0b36bd8 100644
--- a/test/CommandLine.test.ts
+++ b/test/CommandLine.test.ts
@@ -1,23 +1,27 @@
import * as assert from 'assert';
-import { Shell, escapeSpaces } from '../src/CommandLine';
+import { escapeSpaces } from '../src/CommandLine';
+import { Shell } from '../src/Shell';
suite('CommandLine tests', () => {
suite('escape_spaces', () => {
test('does not escape a string if the string has no spaces', () => {
assert.equal(escapeSpaces('/home/user', Shell.Shell), '/home/user');
- assert.equal(escapeSpaces('C:\\User', Shell.CMD), 'C:\\User');
+ assert.equal(escapeSpaces('/home/user', Shell.Wsl), '/home/user');
+ assert.equal(escapeSpaces('C:\\User', Shell.Cmd), 'C:\\User');
assert.equal(escapeSpaces('C:\\User', Shell.PowerShell), 'C:\\User');
});
test('escapes spaces', () => {
assert.equal(escapeSpaces('/home/some user with spaces', Shell.Shell), '\'/home/some user with spaces\'');
+ assert.equal(escapeSpaces('/home/some user with spaces', Shell.Wsl), '\'/home/some user with spaces\'');
assert.equal(escapeSpaces('C:\\Some user with spaces', Shell.PowerShell), 'C:\\Some` user` with` spaces');
- assert.equal(escapeSpaces('C:\\Some user with spaces', Shell.CMD), '"C:\\Some user with spaces"');
+ assert.equal(escapeSpaces('C:\\Some user with spaces', Shell.Cmd), '"C:\\Some user with spaces"');
});
test('does not escape escaped spaces', () => {
assert.equal(escapeSpaces('\'/home/some user with spaces\'', Shell.Shell), '\'/home/some user with spaces\'');
+ assert.equal(escapeSpaces('\'/home/some user with spaces\'', Shell.Wsl), '\'/home/some user with spaces\'');
assert.equal(escapeSpaces('C:\\Some` user` with` spaces', Shell.PowerShell), 'C:\\Some` user` with` spaces');
- assert.equal(escapeSpaces('"C:\\Some user with spaces"', Shell.CMD), '"C:\\Some user with spaces"');
+ assert.equal(escapeSpaces('"C:\\Some user with spaces"', Shell.Cmd), '"C:\\Some user with spaces"');
});
});
});
diff --git a/test/Shell.test.ts b/test/Shell.test.ts
new file mode 100644
index 0000000..fc1423f
--- /dev/null
+++ b/test/Shell.test.ts
@@ -0,0 +1,10 @@
+import * as assert from 'assert';
+import { VALUES, VALUE_STRINGS, fromString } from '../src/Shell';
+
+suite('Shell tests', () => {
+ suite('fromString', () => {
+ test('parses all possible values to expected values', () => {
+ assert.deepEqual(VALUE_STRINGS.map(fromString), VALUES);
+ });
+ });
+});
diff --git a/test/WslShellUtils.test.ts b/test/WslShellUtils.test.ts
new file mode 100644
index 0000000..27f676c
--- /dev/null
+++ b/test/WslShellUtils.test.ts
@@ -0,0 +1,11 @@
+import * as assert from 'assert';
+import { correctPath } from '../src/WslShellUtils';
+
+suite('WslShellUtils tests', () => {
+ suite('correctPath', () => {
+ test('works', () => {
+ assert.equal(correctPath('C:\\Directory'), '/mnt/c/Directory');
+ assert.equal(correctPath('E:\\Some\\Directory'), '/mnt/e/Some/Directory');
+ });
+ });
+});