Remove cargo-watch from vscode extension.

Still keeps tests around for reference when porting them to rust
This commit is contained in:
Emil Lauridsen 2019-12-25 16:44:42 +01:00
parent 66e8ef53a0
commit 41a1ec723c
6 changed files with 1 additions and 454 deletions

View file

@ -18,7 +18,7 @@
"scripts": {
"vscode:prepublish": "npm run compile",
"package": "vsce package",
"compile": "rollup -c && shx cp src/utils/terminateProcess.sh bundle/terminateProcess.sh",
"compile": "rollup -c",
"watch": "tsc -watch -p ./",
"fix": "prettier **/*.{json,ts} --write && tslint --project . --fix",
"lint": "tslint --project .",
@ -133,16 +133,6 @@
"command": "rust-analyzer.reload",
"title": "Restart server",
"category": "Rust Analyzer"
},
{
"command": "rust-analyzer.startCargoWatch",
"title": "Start Cargo Watch",
"category": "Rust Analyzer"
},
{
"command": "rust-analyzer.stopCargoWatch",
"title": "Stop Cargo Watch",
"category": "Rust Analyzer"
}
],
"keybindings": [

View file

@ -1,264 +0,0 @@
import * as child_process from 'child_process';
import * as path from 'path';
import * as vscode from 'vscode';
import { Server } from '../server';
import { terminate } from '../utils/processes';
import { LineBuffer } from './line_buffer';
import { StatusDisplay } from './watch_status';
import {
mapRustDiagnosticToVsCode,
RustDiagnostic,
} from '../utils/diagnostics/rust';
import SuggestedFixCollection from '../utils/diagnostics/SuggestedFixCollection';
import { areDiagnosticsEqual } from '../utils/diagnostics/vscode';
export async function registerCargoWatchProvider(
subscriptions: vscode.Disposable[],
): Promise<CargoWatchProvider | undefined> {
let cargoExists = false;
// Check if the working directory is valid cargo root path
const cargoTomlPath = path.join(vscode.workspace.rootPath!, 'Cargo.toml');
const cargoTomlUri = vscode.Uri.file(cargoTomlPath);
const cargoTomlFileInfo = await vscode.workspace.fs.stat(cargoTomlUri);
if (cargoTomlFileInfo) {
cargoExists = true;
}
if (!cargoExists) {
vscode.window.showErrorMessage(
`Couldn\'t find \'Cargo.toml\' at ${cargoTomlPath}`,
);
return;
}
const provider = new CargoWatchProvider();
subscriptions.push(provider);
return provider;
}
export class CargoWatchProvider implements vscode.Disposable {
private readonly diagnosticCollection: vscode.DiagnosticCollection;
private readonly statusDisplay: StatusDisplay;
private readonly outputChannel: vscode.OutputChannel;
private suggestedFixCollection: SuggestedFixCollection;
private codeActionDispose: vscode.Disposable;
private cargoProcess?: child_process.ChildProcess;
constructor() {
this.diagnosticCollection = vscode.languages.createDiagnosticCollection(
'rustc',
);
this.statusDisplay = new StatusDisplay(
Server.config.cargoWatchOptions.command,
);
this.outputChannel = vscode.window.createOutputChannel(
'Cargo Watch Trace',
);
// Track `rustc`'s suggested fixes so we can convert them to code actions
this.suggestedFixCollection = new SuggestedFixCollection();
this.codeActionDispose = vscode.languages.registerCodeActionsProvider(
[{ scheme: 'file', language: 'rust' }],
this.suggestedFixCollection,
{
providedCodeActionKinds:
SuggestedFixCollection.PROVIDED_CODE_ACTION_KINDS,
},
);
}
public start() {
if (this.cargoProcess) {
vscode.window.showInformationMessage(
'Cargo Watch is already running',
);
return;
}
let args =
Server.config.cargoWatchOptions.command + ' --message-format json';
if (Server.config.cargoWatchOptions.allTargets) {
args += ' --all-targets';
}
if (Server.config.cargoWatchOptions.command.length > 0) {
// Excape the double quote string:
args += ' ' + Server.config.cargoWatchOptions.arguments;
}
// Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes
if (process.platform === 'win32') {
args = '"' + args + '"';
}
const ignoreFlags = Server.config.cargoWatchOptions.ignore.reduce(
(flags, pattern) => [...flags, '--ignore', pattern],
[] as string[],
);
// Start the cargo watch with json message
this.cargoProcess = child_process.spawn(
'cargo',
['watch', '-x', args, ...ignoreFlags],
{
stdio: ['ignore', 'pipe', 'pipe'],
cwd: vscode.workspace.rootPath,
windowsVerbatimArguments: true,
},
);
if (!this.cargoProcess) {
vscode.window.showErrorMessage('Cargo Watch failed to start');
return;
}
const stdoutData = new LineBuffer();
this.cargoProcess.stdout?.on('data', (s: string) => {
stdoutData.processOutput(s, line => {
this.logInfo(line);
try {
this.parseLine(line);
} catch (err) {
this.logError(`Failed to parse: ${err}, content : ${line}`);
}
});
});
const stderrData = new LineBuffer();
this.cargoProcess.stderr?.on('data', (s: string) => {
stderrData.processOutput(s, line => {
this.logError('Error on cargo-watch : {\n' + line + '}\n');
});
});
this.cargoProcess.on('error', (err: Error) => {
this.logError(
'Error on cargo-watch process : {\n' + err.message + '}\n',
);
});
this.logInfo('cargo-watch started.');
}
public stop() {
if (this.cargoProcess) {
this.cargoProcess.kill();
terminate(this.cargoProcess);
this.cargoProcess = undefined;
} else {
vscode.window.showInformationMessage('Cargo Watch is not running');
}
}
public dispose(): void {
this.stop();
this.diagnosticCollection.clear();
this.diagnosticCollection.dispose();
this.outputChannel.dispose();
this.statusDisplay.dispose();
this.codeActionDispose.dispose();
}
private logInfo(line: string) {
if (Server.config.cargoWatchOptions.trace === 'verbose') {
this.outputChannel.append(line);
}
}
private logError(line: string) {
if (
Server.config.cargoWatchOptions.trace === 'error' ||
Server.config.cargoWatchOptions.trace === 'verbose'
) {
this.outputChannel.append(line);
}
}
private parseLine(line: string) {
if (line.startsWith('[Running')) {
this.diagnosticCollection.clear();
this.suggestedFixCollection.clear();
this.statusDisplay.show();
}
if (line.startsWith('[Finished running')) {
this.statusDisplay.hide();
}
interface CargoArtifact {
reason: string;
package_id: string;
}
// https://github.com/rust-lang/cargo/blob/master/src/cargo/util/machine_message.rs
interface CargoMessage {
reason: string;
package_id: string;
message: RustDiagnostic;
}
// cargo-watch itself output non json format
// Ignore these lines
let data: CargoMessage;
try {
data = JSON.parse(line.trim());
} catch (error) {
this.logError(`Fail to parse to json : { ${error} }`);
return;
}
if (data.reason === 'compiler-artifact') {
const msg = data as CargoArtifact;
// The format of the package_id is "{name} {version} ({source_id})",
// https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53
this.statusDisplay.packageName = msg.package_id.split(' ')[0];
} else if (data.reason === 'compiler-message') {
const msg = data.message as RustDiagnostic;
const mapResult = mapRustDiagnosticToVsCode(msg);
if (!mapResult) {
return;
}
const { location, diagnostic, suggestedFixes } = mapResult;
const fileUri = location.uri;
const diagnostics: vscode.Diagnostic[] = [
...(this.diagnosticCollection!.get(fileUri) || []),
];
// If we're building multiple targets it's possible we've already seen this diagnostic
const isDuplicate = diagnostics.some(d =>
areDiagnosticsEqual(d, diagnostic),
);
if (isDuplicate) {
return;
}
diagnostics.push(diagnostic);
this.diagnosticCollection!.set(fileUri, diagnostics);
if (suggestedFixes.length) {
for (const suggestedFix of suggestedFixes) {
this.suggestedFixCollection.addSuggestedFixForDiagnostic(
suggestedFix,
diagnostic,
);
}
// Have VsCode query us for the code actions
vscode.commands.executeCommand(
'vscode.executeCodeActionProvider',
fileUri,
diagnostic.range,
);
}
}
}
}

View file

@ -1,11 +1,7 @@
import * as child_process from 'child_process';
import * as util from 'util';
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import { Server } from '../server';
import { CargoWatchProvider, registerCargoWatchProvider } from './cargo_watch';
interface RunnablesParams {
textDocument: lc.TextDocumentIdentifier;
@ -131,90 +127,3 @@ export async function handleSingle(runnable: Runnable) {
return vscode.tasks.executeTask(task);
}
/**
* Interactively asks the user whether we should run `cargo check` in order to
* provide inline diagnostics; the user is met with a series of dialog boxes
* that, when accepted, allow us to `cargo install cargo-watch` and then run it.
*/
export async function interactivelyStartCargoWatch(
context: vscode.ExtensionContext,
): Promise<CargoWatchProvider | undefined> {
if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') {
return;
}
if (Server.config.cargoWatchOptions.enableOnStartup === 'ask') {
const watch = await vscode.window.showInformationMessage(
'Start watching changes with cargo? (Executes `cargo watch`, provides inline diagnostics)',
'yes',
'no',
);
if (watch !== 'yes') {
return;
}
}
return startCargoWatch(context);
}
export async function startCargoWatch(
context: vscode.ExtensionContext,
): Promise<CargoWatchProvider | undefined> {
const execPromise = util.promisify(child_process.exec);
const { stderr, code = 0 } = await execPromise(
'cargo watch --version',
).catch(e => e);
if (stderr.includes('no such subcommand: `watch`')) {
const msg =
'The `cargo-watch` subcommand is not installed. Install? (takes ~1-2 minutes)';
const install = await vscode.window.showInformationMessage(
msg,
'yes',
'no',
);
if (install !== 'yes') {
return;
}
const label = 'install-cargo-watch';
const taskFinished = new Promise((resolve, _reject) => {
const disposable = vscode.tasks.onDidEndTask(({ execution }) => {
if (execution.task.name === label) {
disposable.dispose();
resolve();
}
});
});
vscode.tasks.executeTask(
createTask({
label,
bin: 'cargo',
args: ['install', 'cargo-watch'],
env: {},
}),
);
await taskFinished;
const output = await execPromise('cargo watch --version').catch(e => e);
if (output.stderr !== '') {
vscode.window.showErrorMessage(
`Couldn't install \`cargo-\`watch: ${output.stderr}`,
);
return;
}
} else if (code !== 0) {
vscode.window.showErrorMessage(
`\`cargo watch\` failed with ${code}: ${stderr}`,
);
return;
}
const provider = await registerCargoWatchProvider(context.subscriptions);
if (provider) {
provider.start();
}
return provider;
}

View file

@ -2,13 +2,8 @@ import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as commands from './commands';
import { CargoWatchProvider } from './commands/cargo_watch';
import { ExpandMacroContentProvider } from './commands/expand_macro';
import { HintsUpdater } from './commands/inlay_hints';
import {
interactivelyStartCargoWatch,
startCargoWatch,
} from './commands/runnables';
import { SyntaxTreeContentProvider } from './commands/syntaxTree';
import * as events from './events';
import * as notifications from './notifications';
@ -139,26 +134,6 @@ export async function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand);
// Executing `cargo watch` provides us with inline diagnostics on save
let provider: CargoWatchProvider | undefined;
interactivelyStartCargoWatch(context).then(p => {
provider = p;
});
registerCommand('rust-analyzer.startCargoWatch', () => {
if (provider) {
provider.start();
} else {
startCargoWatch(context).then(p => {
provider = p;
});
}
});
registerCommand('rust-analyzer.stopCargoWatch', () => {
if (provider) {
provider.stop();
}
});
// Start the language server, finally!
try {
await startServer();

View file

@ -1,51 +0,0 @@
'use strict';
import * as cp from 'child_process';
import ChildProcess = cp.ChildProcess;
import { join } from 'path';
const isWindows = process.platform === 'win32';
const isMacintosh = process.platform === 'darwin';
const isLinux = process.platform === 'linux';
// this is very complex, but is basically copy-pased from VSCode implementation here:
// https://github.com/Microsoft/vscode-languageserver-node/blob/dbfd37e35953ad0ee14c4eeced8cfbc41697b47e/client/src/utils/processes.ts#L15
// And see discussion at
// https://github.com/rust-analyzer/rust-analyzer/pull/1079#issuecomment-478908109
export function terminate(process: ChildProcess, cwd?: string): boolean {
if (isWindows) {
try {
// This we run in Atom execFileSync is available.
// Ignore stderr since this is otherwise piped to parent.stderr
// which might be already closed.
const options: any = {
stdio: ['pipe', 'pipe', 'ignore'],
};
if (cwd) {
options.cwd = cwd;
}
cp.execFileSync(
'taskkill',
['/T', '/F', '/PID', process.pid.toString()],
options,
);
return true;
} catch (err) {
return false;
}
} else if (isLinux || isMacintosh) {
try {
const cmd = join(__dirname, 'terminateProcess.sh');
const result = cp.spawnSync(cmd, [process.pid.toString()]);
return result.error ? false : true;
} catch (err) {
return false;
}
} else {
process.kill('SIGKILL');
return true;
}
}

View file

@ -1,12 +0,0 @@
#!/bin/bash
terminateTree() {
for cpid in $(pgrep -P $1); do
terminateTree $cpid
done
kill -9 $1 > /dev/null 2>&1
}
for pid in $*; do
terminateTree $pid
done