Skip to content
This repository has been archived by the owner on Nov 18, 2022. It is now read-only.

switch exec -> execFile; ShellExecution -> ProcessExecution #683

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 30 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
# Rust support for Visual Studio Code

[![](https://vsmarketplacebadge.apphb.com/version/rust-lang.rust.svg)](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust)
[![Build Status](https://travis-ci.com/rust-lang/rls-vscode.svg?branch=master)](https://travis-ci.com/rust-lang/rls-vscode)
[![Build Status](https://travis-ci.com/AndriesK/rls-vscode.svg?branch=master)](https://travis-ci.com/AndriesK/rls-vscode)

Adds language support for Rust to Visual Studio Code. Supports:

* code completion
* jump to definition, peek definition, find all references, symbol search
* types and documentation on hover
* code formatting
* refactoring (rename, deglob)
* error squiggles and apply suggestions from errors
* snippets
* build tasks

- code completion
- jump to definition, peek definition, find all references, symbol search
- types and documentation on hover
- code formatting
- refactoring (rename, deglob)
- error squiggles and apply suggestions from errors
- snippets
- build tasks

Rust support is powered by the [Rust Language Server](https://github.com/rust-lang/rls)
(RLS). If you don't have it installed, the extension will install it for you.
Expand All @@ -33,20 +32,18 @@ advice.
Contributing code, tests, documentation, and bug reports is appreciated! For
more details on building and debugging, etc., see [contributing.md](contributing.md).


## Quick start

* Install [rustup](https://www.rustup.rs/) (Rust toolchain manager).
* Install this extension from [the VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust)
- Install [rustup](https://www.rustup.rs/) (Rust toolchain manager).
- Install this extension from [the VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust)
(or by entering `ext install rust-lang.rust` at the command palette <kbd>Ctrl</kbd>+<kbd>P</kbd>).
* (Skip this step if you already have Rust projects that you'd like to work on.)
- (Skip this step if you already have Rust projects that you'd like to work on.)
Create a new Rust project by following [these instructions](https://doc.rust-lang.org/book/ch01-03-hello-cargo.html).
* Open a Rust project (`File > Add Folder to Workspace...`). Open the folder for the whole
- Open a Rust project (`File > Add Folder to Workspace...`). Open the folder for the whole
project (i.e., the folder containing 'Cargo.toml'), not the 'src' folder.
* You'll be prompted to install the RLS. Once installed, the RLS should start
- You'll be prompted to install the RLS. Once installed, the RLS should start
building your project.


## Configuration

This extension provides options in VSCode's configuration settings. These
Expand All @@ -57,11 +54,11 @@ have Intellisense help.

Some highlights:

* `rust.show_warnings` - set to false to silence warnings in the editor.
* `rust.all_targets` - build and index code for all targets (i.e., integration tests, examples, and benches)
* `rust.cfg_test` - build and index test code (i.e., code with `#[cfg(test)]`/`#[test]`)
- `rust.show_warnings` - set to false to silence warnings in the editor.
- `rust.all_targets` - build and index code for all targets (i.e., integration tests, examples, and benches)
- `rust.cfg_test` - build and index test code (i.e., code with `#[cfg(test)]`/`#[test]`)

* `rust-client.channel` - specifies from which toolchain the RLS should be spawned
- `rust-client.channel` - specifies from which toolchain the RLS should be spawned

## Features

Expand All @@ -72,16 +69,16 @@ includes snippet names as options when you type; select one by pressing 'enter'.
You can move to the next 'hole' in the template by pressing 'tab'. We provide
the following snippets:

* `for` - a for loop
* `unimplemented`
* `unreachable`
* `print(ln)`
* `assert(_eq)`
* `macro_rules` - declare a macro
* `if let Option` - an `if let` statement for executing code only in the `Some`
- `for` - a for loop
- `unimplemented`
- `unreachable`
- `print(ln)`
- `assert(_eq)`
- `macro_rules` - declare a macro
- `if let Option` - an `if let` statement for executing code only in the `Some`
case.
* `spawn` - spawn a thread
* `extern crate` - insert an `extern crate` statement
- `spawn` - spawn a thread
- `extern crate` - insert an `extern crate` statement

This extension is deliberately conservative about snippets and doesn't include
too many. If you want more, check out
Expand All @@ -97,22 +94,19 @@ The plugin writes these into `tasks.json`. The plugin will not overwrite
existing tasks, so you can customise these tasks. To refresh back to the
defaults, delete `tasks.json` and restart VSCode.


## Format on save

To enable formatting on save, you need to set the `editor.formatOnSave` setting
to `true`. Find it under `File > Preferences > Settings`.


## Requirements

* [Rustup](https://www.rustup.rs/),
* A Rust toolchain (the extension will configure this for you, with
- [Rustup](https://www.rustup.rs/),
- A Rust toolchain (the extension will configure this for you, with
permission),
* `rls`, `rust-src`, and `rust-analysis` components (the
- `rls`, `rust-src`, and `rust-analysis` components (the
extension will install these for you, with permission).


## Implementation

This extension almost exclusively uses the RLS for its feature support (syntax
Expand All @@ -121,4 +115,3 @@ the Rust compiler (`rustc`) to get data about Rust programs. It uses Cargo to
manage building. Both Cargo and `rustc` are run in-process by the RLS. Formatting
and code completion are provided by `rustfmt` and Racer, again both of these are
run in-process by the RLS.

9 changes: 9 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export class RLSConfiguration {
return this.configuration.get('rust-client.rustupPath', 'rustup');
}

public get rustupPathQuotes(): string {
return '"' + this.rustupPath + '"';
}

public get useWSL(): boolean {
return this.configuration.get<boolean>('rust-client.useWSL', false);
}
Expand Down Expand Up @@ -118,6 +122,10 @@ export class RLSConfiguration {
return this.configuration.get<string>('rust-client.rlsPath');
}

public get rlsPathQuotes(): string | undefined {
return this.rlsPath ? '"' + this.rlsPath + '"' : undefined;
}

public get multiProjectEnabled(): boolean {
return this.configuration.get<boolean>(
'rust-client.enableMultiProjectSetup',
Expand All @@ -130,6 +138,7 @@ export class RLSConfiguration {
return {
channel: ignoreChannel ? '' : this.channel,
path: this.rustupPath,
pathQuotes: this.rustupPathQuotes,
useWSL: this.useWSL,
};
}
Expand Down
10 changes: 6 additions & 4 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,9 @@ class ClientWorkspace {
const rustcPrintSysroot = () =>
this.config.rustupDisabled
? wslWrapper.exec('rustc --print sysroot', { env })
: wslWrapper.exec(
`${this.config.rustupPath} run ${this.config.channel} rustc --print sysroot`,
: wslWrapper.execFile(
this.config.rustupPath,
['run', this.config.channel, 'rustc', '--print', 'sysroot'],
{ env },
);

Expand Down Expand Up @@ -416,7 +417,7 @@ class ClientWorkspace {

private async makeRlsProcess(): Promise<child_process.ChildProcess> {
// Run "rls" from the PATH unless there's an override.
const rlsPath = this.config.rlsPath || 'rls';
const rlsPath = this.config.rlsPathQuotes || 'rls';

// We don't need to set [DY]LD_LIBRARY_PATH if we're using rustup,
// as rustup will set it for us when it chooses a toolchain.
Expand Down Expand Up @@ -451,8 +452,9 @@ class ClientWorkspace {
}

const env = await makeRlsEnv();

childProcess = withWsl(config.useWSL).spawn(
config.path,
config.pathQuotes,
['run', config.channel, rlsPath],
{ env, cwd, shell: true },
);
Expand Down
70 changes: 55 additions & 15 deletions src/rustup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ function isInstalledRegex(componentName: string): RegExp {
export interface RustupConfig {
channel: string;
path: string;
pathQuotes: string;
useWSL: boolean;
}

export async function rustupUpdate(config: RustupConfig) {
startSpinner('RLS', 'Updating…');

try {
const { stdout } = await withWsl(config.useWSL).exec(
`${config.path} update`,
);
const { stdout } = await withWsl(config.useWSL).execFile(config.path, [
'update',
]);

// This test is imperfect because if the user has multiple toolchains installed, they
// might have one updated and one unchanged. But I don't want to go too far down the
Expand Down Expand Up @@ -65,13 +66,38 @@ export async function ensureToolchain(config: RustupConfig) {
* Checks for required RLS components and prompts the user to install if it's
* not already.
*/

export async function checkForRls(config: RustupConfig) {
if (await hasRlsComponents(config)) {
return;
}

// Format an easier to understand component install prompt
const componentsNeededUserOutput = REQUIRED_COMPONENTS.map((component, i) => {
if (
REQUIRED_COMPONENTS.length > 1 &&
i === REQUIRED_COMPONENTS.length - 1
) {
return ' and `' + component + '`';
} else if (
i !== 0 &&
i !== REQUIRED_COMPONENTS.length - 1 &&
REQUIRED_COMPONENTS.length > 2
) {
return ', `' + component + '`';
} else {
return '`' + component + '`';
}
});
const clicked = await Promise.resolve(
window.showInformationMessage('RLS not installed. Install?', 'Yes'),
window.showInformationMessage(
`
Rustup
${
componentsNeededUserOutput.length > 1 ? 'components' : 'component'
} ${componentsNeededUserOutput.join('')} not installed. Install?`,
'Yes',
),
);
if (clicked) {
await installRlsComponents(config);
Expand All @@ -83,9 +109,10 @@ export async function checkForRls(config: RustupConfig) {

async function hasToolchain(config: RustupConfig): Promise<boolean> {
try {
const { stdout } = await withWsl(config.useWSL).exec(
`${config.path} toolchain list`,
);
const { stdout } = await withWsl(config.useWSL).execFile(config.path, [
'toolchain',
'list',
]);
return stdout.includes(config.channel);
} catch (e) {
console.log(e);
Expand Down Expand Up @@ -129,7 +156,7 @@ async function tryToInstallToolchain(config: RustupConfig) {
*/
async function listComponents(config: RustupConfig): Promise<string[]> {
return withWsl(config.useWSL)
.exec(`${config.path} component list --toolchain ${config.channel}`)
.execFile(config.path, ['component', 'list', '--toolchain', config.channel])
.then(({ stdout }) =>
stdout
.toString()
Expand All @@ -142,9 +169,20 @@ async function hasRlsComponents(config: RustupConfig): Promise<boolean> {
try {
const components = await listComponents(config);

return REQUIRED_COMPONENTS.map(isInstalledRegex).every(isInstalledRegex =>
components.some(c => isInstalledRegex.test(c)),
);
// Splice the components that are installed from the REQUIRED_COMPONENTS array
for (let i = REQUIRED_COMPONENTS.length; i >= 0; --i) {
const installedRegex = isInstalledRegex(REQUIRED_COMPONENTS[i]);
components.forEach(component => {
if (installedRegex.test(component)) {
REQUIRED_COMPONENTS.splice(i, 1);
}
});
}
if (REQUIRED_COMPONENTS.length === 0) {
return true;
} else {
return false;
}
} catch (e) {
console.log(e);
window.showErrorMessage(`Can't detect RLS components: ${e.message}`);
Expand All @@ -153,7 +191,7 @@ async function hasRlsComponents(config: RustupConfig): Promise<boolean> {
}
}

async function installRlsComponents(config: RustupConfig) {
export async function installRlsComponents(config: RustupConfig) {
startSpinner('RLS', 'Installing components…');

for (const component of REQUIRED_COMPONENTS) {
Expand Down Expand Up @@ -226,7 +264,9 @@ export function parseActiveToolchain(rustupOutput: string): string {
export async function getVersion(config: RustupConfig): Promise<string> {
const versionRegex = /rustup ([0-9]+\.[0-9]+\.[0-9]+)/;

const output = await withWsl(config.useWSL).exec(`${config.path} --version`);
const output = await withWsl(config.useWSL).execFile(config.path, [
'--version',
]);
const versionMatch = output.stdout.toString().match(versionRegex);
if (versionMatch && versionMatch.length >= 2) {
return versionMatch[1];
Expand Down Expand Up @@ -257,7 +297,7 @@ export function getActiveChannel(wsPath: string, config: RustupConfig): string {
try {
// `rustup show active-toolchain` is available since rustup 1.12.0
activeChannel = withWsl(config.useWSL)
.execSync(`${config.path} show active-toolchain`, { cwd: wsPath })
.execSync(`${config.pathQuotes} show active-toolchain`, { cwd: wsPath })
.toString()
.trim();
// Since rustup 1.17.0 if the active toolchain is the default, we're told
Expand All @@ -268,7 +308,7 @@ export function getActiveChannel(wsPath: string, config: RustupConfig): string {
} catch (e) {
// Possibly an old rustup version, so try rustup show
const showOutput = withWsl(config.useWSL)
.execSync(`${config.path} show`, { cwd: wsPath })
.execSync(`${config.pathQuotes} show`, { cwd: wsPath })
.toString();
activeChannel = parseActiveToolchain(showOutput);
}
Expand Down
6 changes: 5 additions & 1 deletion src/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as crypto from 'crypto';
import {
Disposable,
ProcessExecution,
ShellExecution,
Task,
TaskGroup,
Expand Down Expand Up @@ -127,14 +128,17 @@ export async function runTaskCommand(
displayName: string,
folder?: WorkspaceFolder,
) {
if (!command) {
return;
}
const uniqueId = crypto.randomBytes(20).toString();

const task = new Task(
{ label: uniqueId, type: 'shell' },
folder ? folder : workspace.workspaceFolders![0],
displayName,
TASK_SOURCE,
new ShellExecution(`${command} ${args.join(' ')}`, {
new ProcessExecution(command, args, {
cwd: cwd || (folder && folder.uri.fsPath),
env,
}),
Expand Down
4 changes: 4 additions & 0 deletions src/utils/child_process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import * as util from 'util';
import { modifyParametersForWSL } from './wslpath';

const execAsync = util.promisify(child_process.exec);
const execFile = util.promisify(child_process.execFile);

export interface SpawnFunctions {
exec: typeof execAsync;
execFile: typeof execFile;
execSync: typeof child_process.execSync;
spawn: typeof child_process.spawn;
modifyArgs: (
Expand All @@ -19,12 +21,14 @@ export function withWsl(withWsl: boolean): SpawnFunctions {
return withWsl
? {
exec: withWslModifiedParameters(execAsync),
execFile: withWslModifiedParameters(execFile),
execSync: withWslModifiedParameters(child_process.execSync),
spawn: withWslModifiedParameters(child_process.spawn),
modifyArgs: modifyParametersForWSL,
}
: {
exec: execAsync,
execFile,
execSync: child_process.execSync,
spawn: child_process.spawn,
modifyArgs: (command: string, args: string[]) => ({ command, args }),
Expand Down
Loading